Temps de chargement longs pour la page de résumé utilisateur avec une base de données lente

Bonjour,

Nous rencontrons d’énormes temps de chargement lors de l’accès à la page de résumé d’un utilisateur. Il semble que le chargement du résumé prenne d’autant plus de temps que l’utilisateur est actif sur notre site. Ce qui, oui, semble assez évident, mais la différence de temps de chargement sur notre site est immense.
Ainsi, charger la page de résumé d’un « nouveau » utilisateur (quelques semaines d’activité) prend environ 1 seconde, tandis que charger la page d’un utilisateur ayant une activité quotidienne depuis des années prend jusqu’à 30-40 secondes.

Nous avons donc examiné la base de données et avons été assez impressionnés par la taille de la table topic_views (incluant les index), qui atteint 62 Go.

Voici nos 7 plus grandes tables :

relation total_size table_size index_size reltuples
topic_views 62 Go 17 Go 45 Go 399 215 000
posts 27 Go 22 Go 5 022 Mo 9 123 860
top_topics 15 Go 708 Mo 15 Go 1 602 180
post_search_data 15 Go 12 Go 2 849 Mo 8 181 010
incoming_links 12 Go 5 330 Mo 6 647 Mo 90 008 900
user_actions 3 184 Mo 877 Mo 2 307 Mo 11 872 800
topics 2 117 Mo 1 035 Mo 1 082 Mo 1 613 070

Pour tester cela, nous avons sélectionné l’ID d’un utilisateur dont nous savons que le chargement du résumé prend beaucoup de temps, et avons exécuté un comptage avec cet ID sur la table topic_views. Cette requête a pris presque le même temps que le chargement du résumé de l’utilisateur. La partie topics viewed est donc responsable de ce délai.

Comme expliqué dans des sujets antérieurs, nous avons migré un ancien site basé sur Discourse (v1.2.4 + modifications) vers la version v2.4.0-beta.2, et utilisons maintenant Discourse v2.4.0-beta6. Il pourrait donc rester certaines données résiduelles. Cependant, nous ne savons pas ce qui peut être supprimé.

À ce stade, nous sommes assez incertains quant à la prochaine étape ou aux ajustements à effectuer. Notre installation fonctionne entièrement sur Azure, avec une instance externe d’Azure Database for PostgreSQL, et nous avons appliqué la configuration décrite dans votre fichier postgres.10.template.yml. Nous ne savons même pas si la taille de la table topic_views (en particulier l’index) est « trop grande » par rapport aux autres tables ou si cela est tout à fait normal.

Toute indication, réflexion ou idée est la bienvenue.

Cordialement,
Sascha Hofmann

P.S.
J’ai oublié de mentionner que, à cause de cela, nous rencontrons plusieurs erreurs 502 (affichées directement sur le site, donc pas de page d’erreur nginx ou de passerelle d’application) lors du chargement de /u/<username>/summary.json.

Le regardez-vous en tant qu’administrateur / membre du personnel ou en tant qu’anonyme ? La vue du personnel est beaucoup plus coûteuse.

Cela ne fait aucune différence que je consulte /u/<username>/summary en tant qu’administrateur/membre du personnel ou avec mon compte utilisateur normal. Nous avons également activé « masquer les profils utilisateurs au public », de sorte que les profils utilisateurs ne sont pas visibles pour les anonymes (nous l’avons désactivé pour les tests, mais cela ne change rien, car sujets consultés est également présent dans le résumé).

Avez-vous migré, modifié ou reconstruit la même base de données encore et encore ? Il est possible que votre base de données ait besoin d’un VACUUM pour mettre à jour les statistiques. Des requêtes de 30 secondes sont bien en dehors de la plage normale que nous observons dans les installations de Discourse (bien qu’il faille admettre que votre base de données soit volumineuse.)

VACUUM FULL vaut la peine d’être essayé si vous pouvez accepter une certaine interruption de service.

Votre base de données est-elle également sur un stockage SSD rapide avec BEAUCOUP de RAM ? C’est crucial.

Pour savoir exactement quel est le problème :

  1. Modifiez le fichier app.yml et ajoutez votre adresse e-mail d’administrateur à DISCOURSE_DEVELOPER_EMAILS.

  2. Reconstruisez

  3. Chargez la page lente tout en étant connecté en tant que l’utilisateur défini à l’étape 1.

  4. Partagez le résultat du mini-profiler

Partagez le contenu de ceci et de la fenêtre développée :

Nous l’avons testé plusieurs fois, mais toujours avec une copie fraîche de l’ancien jeu de données. Après quelques semaines, et dès que nous étions certains d’avoir annulé les modifications du propriétaire précédent avec succès, nous avons lancé la mise à niveau et la migration une seule fois.

Cela vaut certainement la peine d’être essayé, mais je ne suis pas tout à fait sûr de la durée que cela prendra. Mais oui, nous devrions envisager un vacuum complet. Surtout que nous avons déjà eu des problèmes avec l’ancienne base de données ici.

Oui. Toutes les données sont stockées sur un serveur SSD et soutenues par 40 Go de RAM (dont environ 60 % sont utilisés globalement, sans atteindre 100 % même sous des charges plus élevées).

Merci pour ce conseil, je vais essayer.
Comme nous utilisons notre propre image Docker, où app.yml est absent, nous devrions obtenir le même comportement en ajoutant l’adresse ou les adresses e-mail à developer_emails dans discourse.conf.

Vous pouvez accéder à la console Rails et utiliser Developers.create!(user_id: 123) où 123 est votre identifiant d’utilisateur.

Malheureusement, cela ne fonctionne pas (oui, j’ai bien remplacé 123 avant :slight_smile:) :

discourse@2d5c3bf04550:~$ rails console --environment=production
Chargement de l'environnement de production (Rails 6.0.0)
irb(main):001:0> Developers.create!(user_id: 123)
Traceback (most recent call last):
        1: from (irb):1
NameError (constante Developers non initialisée)

Une autre approche :

discourse@2d5c3bf04550:~$ rails runner --environment=production make_me_dev.rb
Traceback (most recent call last):
/var/www/discourse/bin/rails: Bootsnap::LoadPathCache::FallbackScan
	19: from /var/www/discourse/bin/rails:17:in `<main>'
	18: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `require'
	17: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:291:in `load_dependency'
	16: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/activesupport-6.0.0/lib/active_support/dependencies.rb:325:in `block in require'
	15: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
	14: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi'
	13: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register'
	12: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi'
	11: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require'
	10: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands.rb:18:in `<main>'
	 9: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/command.rb:46:in `invoke'
	 8: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/command/base.rb:65:in `perform'
	 7: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
	 6: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
	 5: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
	 4: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/railties-6.0.0/lib/rails/commands/runner/runner_command.rb:42:in `perform'
	 3: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:52:in `load'
	 2: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:69:in `rescue in load'
	 1: from /var/www/discourse/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:69:in `load'
make_me_dev.rb:1:in `<main>': constante Developers non initialisée (NameError)
discourse@2d5c3bf04550:~$ cat make_me_dev.rb
Developers.create!(user_id: 123)

Je vais essayer la méthode discourse.conf et fournir les données demandées dès que possible.

Merci pour vos conseils jusqu’à présent !

Eh bien, c’est pourquoi nous conseillons toujours aux gens d’utiliser la méthode officielle et prise en charge pour installer Discourse, afin que, lorsque de l’aide est nécessaire, nous puissions intervenir en utilisant des outils standards.

Oui, peut-être est-ce parce que nous utilisons supervisord pour lancer tous les processus.

Quoi qu’il en soit, voici la sortie que vous avez demandée (contenu HTML compressé) :
_u_x_strom_summary.json-13498.2ms-ProfilingResults.zip (94,5 Ko)

Cette requête a pris 13 secondes, mais j’ai dû charger le site deux fois. Encore une fois, la première tentative a pris trop de temps, ce qui a entraîné une erreur 502. Peut-être que certains éléments étaient déjà mis en cache.

L’exécution de 51 requêtes SQL a pris plus de 13 secondes. Votre base de données est soit mal configurée, soit insuffisamment puissante.

Les principaux coupables
SELECT "posts"."id" AS t0_r0, "posts"."user_id" AS t0_r1, "posts"."topic_id" AS t0_r2, "posts"."post_number" AS t0_r3, "posts"."raw" AS t0_r4, "posts"."cooked" AS t0_r5, "posts"."created_at" AS t0_r6, "posts"."updated_at" AS t0_r7, "posts"."reply_to_post_number" AS t0_r8, "posts"."reply_count" AS t0_r9, "posts"."quote_count" AS t0_r10, "posts"."deleted_at" AS t0_r11, "posts"."off_topic_count" AS t0_r12, "posts"."like_count" AS t0_r13, "posts"."incoming_link_count" AS t0_r14, "posts"."bookmark_count" AS t0_r15, "posts"."avg_time" AS t0_r16, "posts"."score" AS t0_r17, "posts"."reads" AS t0_r18, "posts"."post_type" AS t0_r19, "posts"."sort_order" AS t0_r20, "posts"."last_editor_id" AS t0_r21, "posts"."hidden" AS t0_r22, "posts"."hidden_reason_id" AS t0_r23, "posts"."notify_moderators_count" AS t0_r24, "posts"."spam_count" AS t0_r25, "posts"."illegal_count" AS t0_r26, "posts"."inappropriate_count" AS t0_r27, "posts"."last_version_at" AS t0_r28, "posts"."user_deleted" AS t0_r29, "posts"."reply_to_user_id" AS t0_r30, "posts"."percent_rank" AS t0_r31, "posts"."notify_user_count" AS t0_r32, "posts"."like_score" AS t0_r33, "posts"."deleted_by_id" AS t0_r34, "posts"."edit_reason" AS t0_r35, "posts"."word_count" AS t0_r36, "posts"."version" AS t0_r37, "posts"."cook_method" AS t0_r38, "posts"."wiki" AS t0_r39, "posts"."baked_at" AS t0_r40, "posts"."baked_version" AS t0_r41, "posts"."hidden_at" AS t0_r42, "posts"."self_edits" AS t0_r43, "posts"."reply_quoted" AS t0_r44, "posts"."via_email" AS t0_r45, "posts"."raw_email" AS t0_r46, "posts"."public_version" AS t0_r47, "posts"."action_code" AS t0_r48, "posts"."image_url" AS t0_r49, "posts"."locked_by_id" AS t0_r50, "topics"."id" AS t1_r0, "topics"."title" AS t1_r1, "topics"."last_posted_at" AS t1_r2, "topics"."created_at" AS t1_r3, "topics"."updated_at" AS t1_r4, "topics"."views" AS t1_r5, "topics"."posts_count" AS t1_r6, "topics"."user_id" AS t1_r7, "topics"."last_post_user_id" AS t1_r8, "topics"."reply_count" AS t1_r9, "topics"."featured_user1_id" AS t1_r10, "topics"."featured_user2_id" AS t1_r11, "topics"."featured_user3_id" AS t1_r12, "topics"."avg_time" AS t1_r13, "topics"."deleted_at" AS t1_r14, "topics"."highest_post_number" AS t1_r15, "topics"."image_url" AS t1_r16, "topics"."like_count" AS t1_r17, "topics"."incoming_link_count" AS t1_r18, "topics"."category_id" AS t1_r19, "topics"."visible" AS t1_r20, "topics"."moderator_posts_count" AS t1_r21, "topics"."closed" AS t1_r22, "topics"."archived" AS t1_r23, "topics"."bumped_at" AS t1_r24, "topics"."has_summary" AS t1_r25, "topics"."archetype" AS t1_r26, "topics"."featured_user4_id" AS t1_r27, "topics"."notify_moderators_count" AS t1_r28, "topics"."spam_count" AS t1_r29, "topics"."pinned_at" AS t1_r30, "topics"."score" AS t1_r31, "topics"."percent_rank" AS t1_r32, "topics"."subtype" AS t1_r33, "topics"."slug" AS t1_r34, "topics"."deleted_by_id" AS t1_r35, "topics"."participant_count" AS t1_r36, "topics"."word_count" AS t1_r37, "topics"."excerpt" AS t1_r38, "topics"."pinned_globally" AS t1_r39, "topics"."pinned_until" AS t1_r40, "topics"."fancy_title" AS t1_r41, "topics"."highest_staff_post_number" AS t1_r42, "topics"."featured_link" AS t1_r43, "topics"."reviewable_score" AS t1_r44 FROM "posts" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "posts"."topic_id" WHERE ("posts"."deleted_at" IS NULL) AND (posts.post_type IN (1,2,3,4)) AND ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "posts"."user_id" = 643476 AND (post_number > 1) ORDER BY posts.like_count DESC, posts.created_at DESC LIMIT 6; 

Temps d’exécution : 6248,3 ms

SELECT "topic_links"."id" AS t0_r0, "topic_links"."topic_id" AS t0_r1, "topic_links"."post_id" AS t0_r2, "topic_links"."user_id" AS t0_r3, "topic_links"."url" AS t0_r4, "topic_links"."domain" AS t0_r5, "topic_links"."internal" AS t0_r6, "topic_links"."link_topic_id" AS t0_r7, "topic_links"."created_at" AS t0_r8, "topic_links"."updated_at" AS t0_r9, "topic_links"."reflection" AS t0_r10, "topic_links"."clicks" AS t0_r11, "topic_links"."link_post_id" AS t0_r12, "topic_links"."title" AS t0_r13, "topic_links"."crawled_at" AS t0_r14, "topic_links"."quote" AS t0_r15, "topic_links"."extension" AS t0_r16, "topics"."id" AS t1_r0, "topics"."title" AS t1_r1, "topics"."last_posted_at" AS t1_r2, "topics"."created_at" AS t1_r3, "topics"."updated_at" AS t1_r4, "topics"."views" AS t1_r5, "topics"."posts_count" AS t1_r6, "topics"."user_id" AS t1_r7, "topics"."last_post_user_id" AS t1_r8, "topics"."reply_count" AS t1_r9, "topics"."featured_user1_id" AS t1_r10, "topics"."featured_user2_id" AS t1_r11, "topics"."featured_user3_id" AS t1_r12, "topics"."avg_time" AS t1_r13, "topics"."deleted_at" AS t1_r14, "topics"."highest_post_number" AS t1_r15, "topics"."image_url" AS t1_r16, "topics"."like_count" AS t1_r17, "topics"."incoming_link_count" AS t1_r18, "topics"."category_id" AS t1_r19, "topics"."visible" AS t1_r20, "topics"."moderator_posts_count" AS t1_r21, "topics"."closed" AS t1_r22, "topics"."archived" AS t1_r23, "topics"."bumped_at" AS t1_r24, "topics"."has_summary" AS t1_r25, "topics"."archetype" AS t1_r26, "topics"."featured_user4_id" AS t1_r27, "topics"."notify_moderators_count" AS t1_r28, "topics"."spam_count" AS t1_r29, "topics"."pinned_at" AS t1_r30, "topics"."score" AS t1_r31, "topics"."percent_rank" AS t1_r32, "topics"."subtype" AS t1_r33, "topics"."slug" AS t1_r34, "topics"."deleted_by_id" AS t1_r35, "topics"."participant_count" AS t1_r36, "topics"."word_count" AS t1_r37, "topics"."excerpt" AS t1_r38, "topics"."pinned_globally" AS t1_r39, "topics"."pinned_until" AS t1_r40, "topics"."fancy_title" AS t1_r41, "topics"."highest_staff_post_number" AS t1_r42, "topics"."featured_link" AS t1_r43, "topics"."reviewable_score" AS t1_r44, "posts"."id" AS t2_r0, "posts"."user_id" AS t2_r1, "posts"."topic_id" AS t2_r2, "posts"."post_number" AS t2_r3, "posts"."raw" AS t2_r4, "posts"."cooked" AS t2_r5, "posts"."created_at" AS t2_r6, "posts"."updated_at" AS t2_r7, "posts"."reply_to_post_number" AS t2_r8, "posts"."reply_count" AS t2_r9, "posts"."quote_count" AS t2_r10, "posts"."deleted_at" AS t2_r11, "posts"."off_topic_count" AS t2_r12, "posts"."like_count" AS t2_r13, "posts"."incoming_link_count" AS t2_r14, "posts"."bookmark_count" AS t2_r15, "posts"."avg_time" AS t2_r16, "posts"."score" AS t2_r17, "posts"."reads" AS t2_r18, "posts"."post_type" AS t2_r19, "posts"."sort_order" AS t2_r20, "posts"."last_editor_id" AS t2_r21, "posts"."hidden" AS t2_r22, "posts"."hidden_reason_id" AS t2_r23, "posts"."notify_moderators_count" AS t2_r24, "posts"."spam_count" AS t2_r25, "posts"."illegal_count" AS t2_r26, "posts"."inappropriate_count" AS t2_r27, "posts"."last_version_at" AS t2_r28, "posts"."user_deleted" AS t2_r29, "posts"."reply_to_user_id" AS t2_r30, "posts"."percent_rank" AS t2_r31, "posts"."notify_user_count" AS t2_r32, "posts"."like_score" AS t2_r33, "posts"."deleted_by_id" AS t2_r34, "posts"."edit_reason" AS t2_r35, "posts"."word_count" AS t2_r36, "posts"."version" AS t2_r37, "posts"."cook_method" AS t2_r38, "posts"."wiki" AS t2_r39, "posts"."baked_at" AS t2_r40, "posts"."baked_version" AS t2_r41, "posts"."hidden_at" AS t2_r42, "posts"."self_edits" AS t2_r43, "posts"."reply_quoted" AS t2_r44, "posts"."via_email" AS t2_r45, "posts"."raw_email" AS t2_r46, "posts"."public_version" AS t2_r47, "posts"."action_code" AS t2_r48, "posts"."image_url" AS t2_r49, "posts"."locked_by_id" AS t2_r50 FROM "topic_links" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "topic_links"."topic_id" INNER JOIN "posts" ON ("posts"."deleted_at" IS NULL) AND "posts"."id" = "topic_links"."post_id" WHERE (posts.post_type IN (1,2,3,4)) AND ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "topic_links"."user_id" = 643476 AND "topic_links"."internal" = FALSE AND "topic_links"."reflection" = FALSE AND "topic_links"."quote" = FALSE ORDER BY clicks DESC, topic_links.created_at DESC LIMIT 6; 

Temps d’exécution : 258,3 ms

SELECT acting_user_id, COUNT(*) FROM "user_actions" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "user_actions"."target_topic_id" INNER JOIN "posts" ON ("posts"."deleted_at" IS NULL) AND "posts"."id" = "user_actions"."target_post_id" WHERE ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "user_actions"."user_id" = 643476 AND "user_actions"."action_type" = 2 GROUP BY "user_actions"."acting_user_id" ORDER BY COUNT(*) DESC LIMIT 6; 

Temps d’exécution : 245,5 ms

SELECT user_actions.user_id, COUNT(*) FROM "user_actions" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "user_actions"."target_topic_id" INNER JOIN "posts" ON ("posts"."deleted_at" IS NULL) AND "posts"."id" = "user_actions"."target_post_id" WHERE ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "user_actions"."action_type" = 2 AND "user_actions"."acting_user_id" = 643476 GROUP BY "user_actions"."user_id" ORDER BY COUNT(*) DESC LIMIT 6; 

Temps d’exécution : 277,2 ms

SELECT replies.user_id, COUNT(*) FROM "posts" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "posts"."topic_id" JOIN posts replies ON posts.topic_id = replies.topic_id AND posts.reply_to_post_number = replies.post_number WHERE ("posts"."deleted_at" IS NULL) AND (posts.post_type IN (1,2,3,4)) AND ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "posts"."user_id" = 643476 AND (replies.user_id <> 643476) GROUP BY replies.user_id ORDER BY COUNT(*) DESC LIMIT 6; 

Temps d’exécution : 3064,6 ms

SELECT category_id FROM "posts" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "posts"."topic_id" WHERE ("posts"."deleted_at" IS NULL) AND (posts.post_type IN (1,2,3,4)) AND ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "posts"."user_id" = 643476 GROUP BY topics.category_id ORDER BY COUNT(*) DESC LIMIT 6; 

Temps d’exécution : 2283,3 ms

SELECT category_id, COUNT(*) FROM "posts" INNER JOIN "topics" ON ("topics"."deleted_at" IS NULL) AND "topics"."id" = "posts"."topic_id" WHERE ("posts"."deleted_at" IS NULL) AND (posts.post_type IN (1,2,3,4)) AND ("topics"."deleted_at" IS NULL) AND (topics.archetype <> 'private_message') AND "topics"."visible" = TRUE AND (topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE NOT read_restricted OR id IN (1,3,6,100,141,202,229,233,235,248,262,594,595,609))) AND "posts"."user_id" = 643476 AND (post_number > 1) AND (topics.category_id in (64,13,59,124,327,122)) GROUP BY topics.category_id ORDER BY COUNT(*) DESC; 

Temps d’exécution : 811,5 ms

Je suppose qu’Azure propose des statistiques de base de données pour le service hébergé ; vous devez les consulter et évaluer ce qui doit être modifié.

Vérifiez également l’URL /sidekiq de votre installation pour voir s’il n’y a pas de contention de file d’attente qui maintient la base de données occupée.

C’est la première chose que nous avons vérifiée. Notre instance dispose de :

  • 8 cœurs (~16,18 % d’utilisation, avec des pics à 60 %)
  • 40 Go de RAM (~60 % utilisés, sans pics)
  • 2244 IOPS disponibles (~2,5 % d’utilisation, avec des pics à 24 %)

Comme mentionné, nous avons utilisé la configuration décrite dans votre postgres.10.template.yml . Tout le reste est aux paramètres par défaut. Il semble que nous devions y jeter un autre coup d’œil.

Sidekiq semble fonctionner correctement pour le moment. 0 occupé, 0 en file d’attente, 0 réessais, 1 planifié, 0 mort. La liste des tâches est actuellement vide.

Pour en arriver à une première conclusion, nous allons d’abord vérifier la configuration de PostgreSQL, car il existe quelques ajustements que nous pourrions appliquer. Si cela ne fonctionne pas, nous devrons envisager de mettre à niveau l’instance PostgreSQL et/ou effectuer un vacuum complet.

Merci pour votre temps jusqu’à présent et bon week-end !
Cordialement,
Sascha

Si votre machine dispose de 40 Go de RAM, les valeurs par défaut du fichier postgres.10.template.yml ne sont plus adaptées, comme le montre l’exemple dans samples/standalone.yml :

Bonjour,

Merci pour votre réponse. L’utilisation d’une instance PostgreSQL sur Azure présente certaines limitations. Vous ne pouvez pas modifier certains paramètres tels que effective_cache_size, max_connections, maintenance_worker_mem ou shared_buffers.

Au moment de rédiger cette demande de support, nous utilisions le niveau « General Purpose », qui nous offrait 40 Go de RAM, mais seulement 1 Go de shared_buffers et 2,5 Go de effective_cache_size.

Depuis hier, nous sommes passés au niveau « Memory Optimized », ce qui a doublé la RAM à 80 Go, porté shared_buffers à 2 Go et effective_cache_size à 5 Go.

Le paramètre work_mem, que nous pouvons ajuster, est actuellement fixé à 128 Mo, car nous ne constatons aucune entrée de temporary file dans nos journaux.

Ce qui est intéressant, c’est que dès notre changement de niveau, l’utilisation du CPU et des IOPS a diminué, sans augmentation des vCores ni du stockage. Cela confirme que nous avions un problème lié à des caches et des tampons trop petits.

Nous constatons toujours des temps de chargement longs sur les pages de résumé de certains utilisateurs (en particulier les utilisateurs du personnel ou les utilisateurs de longue date) ou sur la page about, mais la situation s’est nettement améliorée par rapport à avant (environ 50 % des temps de chargement initiaux).

Cordialement,

P.S.
Pour ceux qui rencontrent des problèmes similaires et disposent de plus de contrôle sur leur installation PostgreSQL, ces guides pourraient vous être utiles :

Architecture and Tuning of Memory in PostgreSQL Databases | Severalnines

C’est bon à savoir. Les prochaines étapes consisteront à voir si vous pouvez vous en sortir en utilisant une instance à 4 vCPU optimisée pour la mémoire afin d’optimiser les coûts, ou à passer à une gestion autonome.

Ah, nous utilisons 8 cœurs virtuels, ce qui donne 80 Go de RAM. shared_buffers est réglé à 2 Go, mais devrait être à 16 Go pour ce niveau et ce nombre de cœurs. Nous sommes en contact avec le support Azure pour régler cela.

P.S.
Nous sommes déjà sur shared_buffers à 16 Go. J’ai mal interprété la sortie de SELECT * FROM pg_settings WHERE name = 'shared_buffers';. L’unité est 8ko.

Donc je pense que ce sujet peut être clos. Je vous remercie tous pour vos efforts et j’espère que nous pourrons obtenir de meilleures performances dans les prochains jours, en collaboration avec le support Azure.

P.P.S.
Désolé @riking, je t’ai mal compris. Je pense que nous pouvons rester sur 8 cœurs virtuels au lieu de les réduire à 4. Pourrions-nous essayer dès que nous aurons atteint les performances recherchées ?