Долгая загрузка страницы сводки пользователя из-за медленной базы данных

Здравствуйте,

у нас возникла проблема с огромным временем загрузки страницы сводки пользователя. Кажется, что чем активнее пользователь взаимодействует с нашим сайтом, тем дольше загружается его сводка. Да, это кажется очевидным, но разница во времени загрузки на нашем сайте колоссальна.

Так, загрузка страницы сводки «нового» пользователя (с активностью в несколько недель) занимает около 1 секунды, а загрузка страницы пользователя, который активен ежедневно на протяжении нескольких лет, занимает от 30 до 40 секунд.

Мы проверили базу данных и были поражены размером таблицы topic_views (включая индексы) — 62 ГБ.

Вот наши 7 самых больших таблиц:

relation total_size table_size index_size reltuples
topic_views 62 ГБ 17 ГБ 45 ГБ 399215000
posts 27 ГБ 22 ГБ 5022 МБ 9123860
top_topics 15 ГБ 708 МБ 15 ГБ 1602180
post_search_data 15 ГБ 12 ГБ 2849 МБ 8181010
incoming_links 12 ГБ 5330 МБ 6647 МБ 90008900
user_actions 3184 МБ 877 МБ 2307 МБ 11872800
topics 2117 МБ 1035 МБ 1082 МБ 1613070

Для проверки мы взяли идентификатор пользователя, у которого мы знаем, что загрузка сводки занимает очень много времени, и выполнили запрос подсчёта (COUNT) с этим ID в таблице topic_views. Этот запрос занял почти столько же времени, сколько и загрузка сводки пользователя. Таким образом, именно часть «просмотренные темы» отвечает за эту задержку.

Как мы объясняли в предыдущих обсуждениях, мы обновили старый сайт на базе Discourse (версия 1.2.4 с модификациями) до версии v2.4.0-beta.2, а сейчас работаем на версии Discourse v2.4.0-beta6. Возможно, остались какие-то данные от предыдущей версии, но мы не знаем, что именно можно удалить.

На данный момент мы не уверены, что делать или настраивать дальше. Наша установка работает полностью в Azure с использованием внешнего экземпляра Azure Database for PostgreSQL, и мы применили конфигурацию, описанную в вашем файле postgres.10.template.yml. Мы даже не знаем, является ли размер таблицы topic_views (особенно её индекса) «слишком большим» по сравнению с другими таблицами или это нормально.

Будем признательны за любые подсказки, мысли или идеи.

С уважением,
Саша Хофман

P.S.
Забыл упомянуть, что из-за этого у нас возникает несколько ошибок 502 (отображаемых прямо на сайте, то есть это не страница ошибки nginx или шлюза приложения) при загрузке /u/<username>/summary.json.

Вы просматриваете это как администратор / сотрудник или как аноним? Просмотр для сотрудников значительно дороже.

Не имеет значения, просматриваю ли я /u/<username>/summary как администратор/сотрудник или с моей обычной учётной записи. У нас также включена настройка «Скрыть профили пользователей от публичного доступа», поэтому профили пользователей не видны анонимным пользователям (я отключил её для тестирования, но это не меняет ничего, так как раздел «Просмотренные темы» также присутствует в сводке).

Вы неоднократно переносили, изменяли или перестраивали одну и ту же базу данных? Возможно, вашей базе данных требуется вакуум для обновления статистики. Запросы длительностью 30 секунд выходят далеко за рамки нормального диапазона, который мы наблюдаем в установках Discourse (хотя, признаем, ваша база данных велика).

VACUUM FULL стоит попробовать, если вы можете допустить некоторый простой.

Также находится ли ваша база данных на быстром SSD-накопителе с большим объемом оперативной памяти? Это критически важно.

Чтобы точно определить проблему:

  1. Отредактируйте файл app.yml и добавьте email вашего администратора в DISCOURSE_DEVELOPER_EMAILS.

  2. Пересоберите проект.

  3. Откройте медленную страницу, войдя в систему как пользователь, указанный на шаге 1.

  4. Предоставьте результат мини-профайлера.

Поделитесь содержимым этого окна и развернутого окна:

Мы тестировали это несколько раз, но всегда с новой копией старого набора данных. Через пару недель, как только мы убедились, что успешно откатили изменения предыдущего владельца, мы один раз выполнили обновление и миграцию.

Это определённо стоит попробовать, но я не совсем уверен, сколько времени это займёт. Однако да, нам стоит рассмотреть полную очистку (vacuum). Особенно учитывая, что у нас уже были проблемы со старой базой данных ранее.

Да. Все данные хранятся на серверном SSD и поддерживаются 40 ГБ оперативной памяти (из которых в целом используется около 60%, без достижения плато на 100% даже при повышенной нагрузке).

Спасибо за этот совет, я попробую.
Поскольку мы используем свой собственный образ Docker, где отсутствует app.yml, мы должны добиться того же поведения, добавив адрес(а) электронной почты в developer_emails в файле discourse.conf.

Вы можете зайти в консоль Rails и выполнить Developers.create!(user_id: 123), где 123 — это ваш идентификатор пользователя.

К сожалению, это не работает (да, я уже заменил 123 ранее :slight_smile:) :

discourse@2d5c3bf04550:~$ rails console --environment=production
Загрузка среды production (Rails 6.0.0)
irb(main):001:0> Developers.create!(user_id: 123)
Traceback (most recent call last):
        1: from (irb):1
NameError (неопределенная константа Developers)

Другой подход:

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>': uninitialized constant Developers (NameError)
discourse@2d5c3bf04550:~$ cat make_me_dev.rb
Developers.create!(user_id: 123)

Я попробую способ с discourse.conf и предоставлю запрошенные данные как можно скорее.

Спасибо за ваши советы на данный момент!

Именно поэтому мы всегда рекомендуем людям использовать официально поддерживаемый метод установки Discourse. Тогда, когда требуется помощь, мы сможем оказать её, используя стандартные инструменты.

Да, возможно, это связано с тем, что мы используем supervisord для запуска всех процессов.

В любом случае, вот запрошенный вами вывод (архив с HTML-контентом):
_u_x_strom_summary.json-13498.2ms-ProfilingResults.zip (94,5 КБ)

Этот запрос занял 13 секунд, но мне пришлось загрузить сайт дважды. Опять же, первая попытка заняла слишком много времени, что привело к ошибке 502. Возможно, некоторые данные уже были закэшированы.

Выполнение 51 SQL-запроса заняло более 13 секунд. Ваша база данных либо серьёзно неправильно настроена, либо недостаточно производительна.

Основные виновники
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; 

Время выполнения: 6248,3 мс

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; 

Время выполнения: 258,3 мс

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; 

Время выполнения: 245,5 мс

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; 

Время выполнения: 277,2 мс

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; 

Время выполнения: 3064,6 мс

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; 

Время выполнения: 2283,3 мс

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; 

Время выполнения: 811,5 мс

Предполагаю, что в Azure есть какая-то статистика по базе данных для размещённого сервиса — вам нужно проверить её и оценить, что требуется изменить.

Также проверьте URL /sidekiq на вашем сервере, чтобы убедиться, что нет конкуренции в очередях, которая держит базу данных занятой.

Это первое, что мы проверили. Наш экземпляр имеет:

  • 8 ядер (~16,18% загрузки, пики до 60%)
  • 40 ГБ ОЗУ (~60% использовано, без пиков)
  • 2244 доступных IOPS (~2,5% загрузки, пики до 24%)

Как уже говорилось, мы использовали конфигурацию, описанную в вашем файле postgres.10.template.yml. Всё остальное установлено по умолчанию. Похоже, нам стоит ещё раз внимательнее посмотреть на это.

В данный момент Sidekiq работает хорошо: 0 занятых, 0 в очереди, 0 повторных попыток, 1 запланированная задача, 0 мёртвых. Список задач в настоящее время пуст.

Итак, чтобы сделать первый вывод. Сначала мы проверим конфигурацию PostgreSQL, так как есть некоторые настройки, которые мы можем применить. Если это не поможет, мы должны рассмотреть возможность обновления экземпляра PostgreSQL и/или выполнения полной операции VACUUM.

Спасибо за уделённое время и приятных выходных!
С уважением,
Саша

Если на машине установлено 40 ГБ оперативной памяти, параметры по умолчанию в файле postgres.10.template.yml больше не подходят, как указано в samples/standalone.yml:

Здравствуйте,

Спасибо за ваш ответ. При использовании экземпляра PostgreSQL в Azure существуют некоторые ограничения. Вы не можете изменить такие настройки, как effective_cache_size, max_connections, maintenance_worker_mem или shared_buffers.

На момент написания этого запроса в службу поддержки мы использовали тарифный план “General Purpose”, который предоставлял 40 ГБ оперативной памяти, но только 1 ГБ для shared_buffers и 2,5 ГБ для effective_cache_size.

Со вчерашнего дня мы перешли на тарифный план “Memory Optimized”, что увеличило объем оперативной памяти вдвое — до 80 ГБ, а также увеличило shared_buffers до 2 ГБ и effective_cache_size до 5 ГБ.

Настройка work_mem, которую мы можем изменять, в настоящее время установлена на 128 МБ, так как в наших логах нет записей о temporary file.

Интересно, что сразу после смены тарифного плана загрузка процессора и использование IOPS снизились, несмотря на то, что количество vCore и объем хранилища не увеличивались. Это однозначно указывает на то, что проблема была вызвана недостаточным размером кэшей и буферов.

По-прежнему наблюдаются длительные времена загрузки на страницах с обобщенной информацией о некоторых пользователях (особенно о сотрудниках или пользователях с длительным стажем) или на странице about, однако ситуация значительно улучшилась по сравнению с предыдущим периодом (время загрузки сократилось примерно на 50%).

С уважением,

P.S.
Для тех, кто сталкивается с подобными проблемами и имеет больше контроля над своей установкой PostgreSQL, могут оказаться полезными следующие небольшие руководства:

Рад это слышать. Следующие шаги: проверить, можно ли сэкономить, используя 4 vCPU с оптимизацией под память, или перейти на самостоятельное развертывание.

О, у нас запущено 8 vCores, что даёт 80 ГБ оперативной памяти. Параметр shared_buffers установлен на 2 ГБ, но для этого уровня и количества vCores он должен быть 16 ГБ. Однако мы уже связались со службой поддержки Azure, чтобы исправить это.

P.S.
На самом деле shared_buffers у нас уже на 16 ГБ. Я неправильно интерпретировал вывод команды SELECT * FROM pg_settings WHERE name = 'shared_buffers';. Единица измерения — 8 КБ.

Поэтому, думаю, эту тему можно закрыть. Благодарю всех за усилия и надеюсь, что в ближайшие дни вместе со службой поддержки Azure нам удастся достичь лучшей производительности.

P.P.S.
Извините, @riking, я вас неправильно понял. Кажется, нам лучше оставить 8 vCores вместо уменьшения до 4. Давайте попробуем, как только достигнем желаемой производительности.