Аномально высокая загрузка процессора

Ещё одна последняя деталь, после чего я, думаю, уйду на несколько часов.

root@discourse_app:/# ps aux --sort=-%mem | head -20
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
postgres    4398 17.0 22.0 8216352 6797764 ?     Ss   11:14   9:53 postgres: 15/main: discourse discourse [local] UPDATE
postgres    2729 15.3 21.1 8123888 6517308 ?     Ss   11:13   9:03 postgres: 15/main: discourse discourse [local] UPDATE
postgres    2501 15.9 19.9 8079700 6148812 ?     Ds   11:13   9:25 postgres: 15/main: discourse discourse [local] UPDATE
postgres   22777 16.9 19.5 8084888 6012052 ?     Ds   11:42   4:58 postgres: 15/main: discourse discourse [local] UPDATE
postgres    2753 28.5 11.3 8055000 3482260 ?     Ss   11:13  16:50 postgres: 15/main: discourse discourse [local] idle
postgres   25715  2.9  6.9 7884064 2135536 ?     Ss   11:47   0:44 postgres: 15/main: discourse discourse [local] idle
postgres   20487  2.9  6.6 7885300 2061088 ?     Ss   11:39   0:59 postgres: 15/main: discourse discourse [local] idle
postgres   22055  3.3  6.5 7887336 2012504 ?     Ss   11:41   1:02 postgres: 15/main: discourse discourse [local] idle
postgres   25883  2.5  6.0 7884096 1848424 ?     Ss   11:47   0:38 postgres: 15/main: discourse discourse [local] idle
postgres   28126  2.4  5.6 7883848 1744912 ?     Ss   11:50   0:31 postgres: 15/main: discourse discourse [local] idle
postgres   29365  1.0  4.5 7883084 1386544 ?     Ss   11:52   0:12 postgres: 15/main: discourse discourse [local] idle
postgres   27172  1.6  4.4 7884288 1384664 ?     Ss   11:49   0:22 postgres: 15/main: discourse discourse [local] idle
postgres   25896  2.1  4.4 8034236 1357264 ?     Ss   11:47   0:31 postgres: 15/main: discourse discourse [local] idle
postgres      89  1.7  4.3 7864156 1342760 ?     Ss   11:11   1:04 postgres: 15/main: checkpointer
postgres   28505  1.0  4.2 7884360 1315360 ?     Ss   11:51   0:13 postgres: 15/main: discourse discourse [local] idle
postgres   27175  1.6  4.1 7882780 1277612 ?     Ss   11:49   0:23 postgres: 15/main: discourse discourse [local] idle
postgres   28553  0.9  3.4 7883976 1064964 ?     Ss   11:51   0:11 postgres: 15/main: discourse discourse [local] idle
postgres   30409  1.0  3.3 7882892 1034860 ?     Ss   11:54   0:10 postgres: 15/main: discourse discourse [local] idle
postgres   40651  4.6  1.9 7872036 592152 ?      Ss   12:11   0:03 postgres: 15/main: discourse discourse [local] idle
root@discourse_app:/# redis-cli info memory
# Memory
used_memory:179899224
used_memory_human:171.57M
used_memory_rss:47591424
used_memory_rss_human:45.39M
used_memory_peak:184509776
used_memory_peak_human:175.96M
used_memory_peak_perc:97.50%
used_memory_overhead:3681093
used_memory_startup:948600
used_memory_dataset:176218131
used_memory_dataset_perc:98.47%
allocator_allocated:181437808
allocator_active:182353920
allocator_resident:188317696
allocator_muzzy:0
total_system_memory:31537295360
total_system_memory_human:29.37G
used_memory_lua:58368
used_memory_vm_eval:58368
used_memory_lua_human:57.00K
used_memory_scripts_eval:10304
number_of_cached_scripts:13
number_of_functions:0
number_of_libraries:0
used_memory_vm_functions:33792
used_memory_vm_total:92160
used_memory_vm_total_human:90.00K
used_memory_functions:192
used_memory_scripts:10496
used_memory_scripts_human:10.25K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
allocator_frag_ratio:1.00
allocator_frag_bytes:700208
allocator_rss_ratio:1.03
allocator_rss_bytes:5963776
rss_overhead_ratio:0.25
rss_overhead_bytes:-140726272
mem_fragmentation_ratio:0.26
mem_fragmentation_bytes:-132268896
mem_not_counted_for_evict:0
mem_replication_backlog:0
mem_total_replication_buffers:0
mem_clients_slaves:0
mem_clients_normal:498197
mem_cluster_links:0
mem_aof_buffer:0
mem_allocator:jemalloc-5.3.0
mem_overhead_db_hashtable_rehashing:0
active_defrag_running:0
lazyfree_pending_objects:0
lazyfreed_objects:0
root@discourse_app:/# cat /etc/postgresql/15/main/postgresql.conf | grep shared_buffers
shared_buffers = 7424MB
#wal_buffers = -1                       # min 32kB, -1 sets based on shared_buffers
root@discourse_app:/# su - postgres -c "psql discourse -c \"SELECT pid, query_start, state, wait_event_type, wait_event, left(query, 100) as query FROM pg_stat_activity WHERE state != 'idle' ORDER BY query_start;\""
  pid  |          query_start          | state  | wait_event_type |  wait_event  |                                                query
-------+-------------------------------+--------+-----------------+--------------+------------------------------------------------------------------------------------------------------
  2501 | 2026-02-07 11:25:01.028892+00 | active | IO              | DataFileRead | UPDATE posts                                                                                        +
       |                               |        |                 |              | SET percent_rank = X.percent_rank                                                                   +
       |                               |        |                 |              | FROM (                                                                                              +
       |                               |        |                 |              |   SELECT posts.id, Y.percent_rank                                                                   +
       |                               |        |                 |              |   FROM posts
  4398 | 2026-02-07 11:52:53.108942+00 | active | IPC             | BufferIO     | WITH eligible_users AS (                                                                            +
       |                               |        |                 |              |   SELECT id                                                                                         +
       |                               |        |                 |              |   FROM users                                                                                        +
       |                               |        |                 |              |   WHERE id > 0 AND active AND silenced_till IS NUL
  2729 | 2026-02-07 11:54:27.666129+00 | active | IPC             | BufferIO     | UPDATE topics AS topics                                                                             +
       |                               |        |                 |              | SET has_summary = (topics.like_count >= 1 AND                                                       +
       |                               |        |                 |              |                    topics.post
 22777 | 2026-02-07 11:59:27.040575+00 | active | IO              | DataFileRead | UPDATE posts                                                                                        +
       |                               |        |                 |              | SET percent_rank = X.percent_rank                                                                   +
       |                               |        |                 |              | FROM (                                                                                              +
       |                               |        |                 |              |   SELECT posts.id, Y.percent_rank                                                                   +
       |                               |        |                 |              |   FROM posts
 27172 | 2026-02-07 12:15:42.50553+00  | active | IO              | DataFileRead | SELECT "posts"."id" FROM "posts" WHERE "posts"."deleted_at" IS NULL AND "posts"."topic_id" = 792311
 25883 | 2026-02-07 12:15:52.665883+00 | active |                 |              | SELECT "posts"."id" FROM "posts" WHERE "posts"."deleted_at" IS NULL AND "posts"."topic_id" = 829626
 20487 | 2026-02-07 12:16:09.733384+00 | active | IO              | DataFileRead | SELECT "posts"."id" FROM "posts" WHERE "posts"."deleted_at" IS NULL AND "posts"."topic_id" = 653216
 42185 | 2026-02-07 12:16:21.053706+00 | active | IO              | DataFileRead | SELECT "posts"."id", "posts"."user_id", "posts"."topic_id", "posts"."post_number", "posts"."raw", "p
 43940 | 2026-02-07 12:16:21.925505+00 | active |                 |              | SELECT pid, query_start, state, wait_event_type, wait_event, left(query, 100) as query FROM pg_stat_
 28126 | 2026-02-07 12:16:21.96218+00  | active | IO              | DataFileRead | SELECT "posts"."id" FROM "posts" WHERE "posts"."deleted_at" IS NULL AND "posts"."topic_id" = 818063
 42323 | 2026-02-07 12:16:21.966689+00 | active | Client          | ClientRead   | SELECT "discourse_post_event_events"."id", "discourse_post_event_events"."status", "discourse_post_e
(11 rows)

Мой вопрос, по сути, сводится к следующему: «что может вызвать выполнение запроса UPDATE, который зависает на 9 часов?».

Я бы предположил, что дело в нехватке памяти: запросы уходят в своп.
Может ли таблица posts размером 40 ГБ стать потенциальной проблемой?


root@discourse_app:/# su - postgres -c "psql discourse -c \"SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC LIMIT 10;\""
 schemaname |     tablename     |  size
------------+-------------------+---------
 public     | posts             | 40 GB
 public     | post_search_data  | 4326 MB
 public     | topic_users       | 1306 MB
 public     | topics            | 837 MB
 public     | topic_search_data | 702 MB
 public     | post_replies      | 567 MB
 public     | top_topics        | 512 MB
 public     | user_actions      | 417 MB
 public     | topic_links       | 285 MB
 public     | directory_items   | 243 MB

Пробовали ли вы настроить параметры памяти PostgreSQL?

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

Да, мы уже сталкивались с подобным ранее — проблема была явно связана с нехваткой памяти у PostgreSQL.

На этот раз симптомы иные, хотя мы не исключаем возможность похожей проблемы.

Сейчас многие признаки указывают на то, что некоторые структуры потоков оказываются слишком тяжелыми для внутренних задач Discourse. Эта установка возникла в результате самостоятельной миграции с форума vbb. Хочу ещё раз отметить: два года у нас не было никаких проблем, затем в прошлом году нам пришлось настроить параметры PostgreSQL, после чего всё работало гладко примерно 10 дней назад, и снова начались проблемы через несколько дней после обновления до последней версии.

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

Поэтому у меня возникает ещё один вопрос: давно я заметил, что Discourse широко используется для крупных коммерческих решений (например, Activision Blizzard). Понимаю, что это платные установки, команда Discourse получает оплату за надлежащую поддержку, и есть возможность решить проблему за деньги, но не могу не задаться вопросом: насколько большой может быть их таблица сообщений? Я не думаю, что она может быть меньше нашей. Тем не менее, меня удивило бы, если бы самохостинговое решение испытывало проблемы при нашей активной нагрузке (~150 активных пользователей, ~2000 новых постов в день, примерно).

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

Поэтому я задаюсь вопросом: есть ли способ исключить часть данных, которые часто читаются (темы?) и обновляются (статистика пользователей?), например, заблокировав темы, присвоив неактивным пользователям уровень доверия 0 или что-то в этом роде.

Мне кажется, это верное направление мысли: у вас может быть много оперативной памяти, но PostgreSQL настроен на её использование определённым образом и, возможно, сам себе создаёт трудности.

Мне не нравится, что есть два запроса UPDATE, которые выглядят идентично — похоже, что это запланированная задача, которая выполнялась так долго, что вторая была запланирована сразу после неё. Это создаст дополнительную нагрузку в ситуации, когда система и так работает плохо.

Но я не знаю, как именно выполнить эти настройки.

В прошлый раз мы решили возникшую проблему, выделив Postgres больше места — в сложившейся ситуации он использовал слишком мало процессов и слишком мало рабочей памяти.

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

Мне интересно посмотреть, что вы найдете. Я работал администратором Linux, но не администратором баз данных. В обеих областях нужно знать огромное количество информации.

В документации PostgreSQL я нашел:

и

Заметил, что при настройке Discourse несколько параметров PostgreSQL конфигурируются в зависимости от объема оперативной памяти. Обратите внимание: если вы увеличите мощность сервера, эти параметры не будут автоматически подстроены под больший объем RAM.

Мне кажется, что многие рекомендации и практики устарели со времен, когда 2 ГБ считались большим сервером. Для современного оборудования вполне могут подходить более высокие значения.

Редактирование: на моих серверах (4 ГБ) я отключаю большие страницы. Мне кажется, что прозрачные (автоматические) большие страницы могут заставлять ядро тратить время на их объединение и разделение. Однако в документации PostgreSQL указано, что большие страницы могут быть полезны в некоторых ситуациях.

На моей системе:

root@ubuntu-4gb-hel1-1:~# egrep Huge /proc/meminfo
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
root@ubuntu-4gb-hel1-1:~# egrep huge /proc/filesystems
nodev	hugetlbfs
root@ubuntu-4gb-hel1-1:~# 
root@ubuntu-4gb-hel1-1:~# head -v /proc/sys/vm/nr_hugepages /proc/sys/vm/nr_overcommit_hugepages
==> /proc/sys/vm/nr_hugepages <==
0

==> /proc/sys/vm/nr_overcommit_hugepages <==
0
root@ubuntu-4gb-hel1-1:~# egrep huge /sys/devices/system/node/node*/meminfo
root@ubuntu-4gb-hel1-1:~# 

Если только что-то не изменилось, запуск команды ./discourse-setup должен скорректировать эти значения, но при этом он должен определить, что установка уже работает. Если я не ошибаюсь, он также создаёт резервную копию файла app.yml перед внесением изменений.

Не знаю, изменилось ли это недавно, так как последний раз я делал это 3–4 года назад.

Насколько я помню, он меняет лишь такие параметры, как разделяемая память (25% от максимально доступной памяти) и количество воркеров Unicorn. Возможно, я что-то упускаю.

(не своп, но они читают с диска)

это согласуется с моими более ранними наблюдениями:

Обратите внимание, что это размер отношения и индексов. Сравните с pg_relation_size.

Это из ScoreCalculator, часть PeriodicalUpdates.

Это ваше наблюдение, которое необходимо решить. Для сравнения, здесь, на meta, Jobs::EnsureDbConsistency выполняется за <2 минуты, а Jobs::TopRefreshOlder — за <10 секунд:

Postgres нуждается в большем объеме памяти. Выделите ему как можно больше.

Также может помочь выполнение VACUUM ANALYZE или VACUUM ANALYZE FULL. Выполнение первого никогда не вредит.

Я бы рекомендовал выполнить в следующем порядке:

  • vacuum analyze
  • приостановить Sidekiq, затем выполнить vacuum analyze full (это замораживает таблицы для их полной перезаписи, что может привести к некоторым сбоям во время выполнения)
  • увеличить объем памяти для PostgreSQL

У меня несколько раз не удавалось выполнить восстановление или обновление во время миграции из-за нехватки места, несмотря на наличие нескольких гигабайт свободного пространства (например, Restore fails due to disk space on migration because of 70M calendar events).

Может ли выполнение вакуумизации перед резервным копированием помочь решить эту проблему?

Также

Это

 discourse=# VACUUM FULL ANALYZE;

Вот хороший совет по этому поводу, я думаю:

Вероятно, нет, так как PostgreSQL освобождает «дыры» в файлах, но это может помочь. Вряд ли это навредит, так что можно попробовать и сообщить о результатах :+1:

Представляю очередное обновление.

Очистка (vacuum) и настройка расписания очистки внесли изменения: пики использования ресурсов остаются такими же «высокими», как и раньше, но по крайней мере они не такие «широкие», как в прошлом месяце.
Это не решает проблему.

Операции, затрагивающие большие части структуры таблицы (например, доступ к профилю пользователя или перемещение сообщений), иногда завершаются ошибкой 502.

Стремясь сохранить текущую структуру форума, мы перенесём его на выделенный сервер как можно скорее.

Спасибо за всю помощь до сих пор.