Utilisation du CPU anormalement élevée

Une dernière petite information, puis je pense que je serai absent pendant quelques heures.

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)

Ma question, en gros, se résume à : « qu’est-ce qui pourrait créer une requête UPDATE qui reste bloquée pendant 9 heures ? ».

J’émettrais l’hypothèse de ne pas avoir assez de mémoire : les requêtes passent en swap.
Avoir une table de 40 Go de messages est-il un problème potentiel ?


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

Avez-vous essayé d’ajuster les paramètres de mémoire de postgres ?

Si le passage à une nouvelle VM résolvait quoi que ce soit, ce n’est pas grave et peut être fait sans temps d’arrêt et avec juste un peu de temps en lecture seule.

1 « J'aime »

Oui, nous l’avons déjà fait une fois car nous avions un problème un peu similaire - c’était clairement Postgres qui manquait de mémoire.

Cette fois, les symptômes sont différents - cela ne signifie pas que nous excluons la possibilité d’avoir un problème similaire, cependant.

Actuellement, de nombreux indices suggèrent que certaines structures de threads sont trop lourdes pour les tâches internes de Discourse : cette installation provient d’une conversion DIY à partir d’un forum vbb - et je dois réaffirmer que nous n’avons eu pratiquement aucun problème pendant 2 années consécutives, puis l’année dernière, nous avons dû ajuster les paramètres de postgres, puis tout est rentré dans l’ordre jusqu’à il y a environ 10 jours, et ensuite à nouveau après quelques jours de la dernière mise à niveau de version.

Le fait est que nous avons de nombreux fils de discussion anciens que Discourse n’a pas divisés en blocs de 5000 messages, nous avons des milliers de comptes, et je commence à penser que l’historique des publications importé précédemment + l’utilisation normale ont atteint un seuil où notre matériel et l’architecture de Discourse ont du mal à gérer les opérations normales.

Donc, une autre des questions que je me pose en ce moment, c’est que j’ai remarqué depuis longtemps que Discourse est un logiciel de forum largement utilisé pour d’énormes solutions commerciales (par exemple, Activision Blizzard). Je comprends que ce sont des installations payantes et que l’équipe Discourse est payée pour offrir un support approprié, et il y a l’option de jeter de l’argent sur le problème, mais je ne peux m’empêcher de me demander quelle taille leur table de messages peut avoir, et je ne pense pas qu’elle puisse être plus petite que la nôtre. Pourtant, je serais surpris qu’une solution auto-hébergée ait des problèmes avec l’utilisation active que nous avons (~150 utilisateurs actifs, ~2k nouveaux messages par jour, plus ou moins).

De plus, le soir où nous avons mis le forum en lecture seule, le forum était d’une rapidité fulgurante. Évidemment, l’activité des utilisateurs, qui comprend la publication mais va au-delà, est ce qui cause la situation que nous avons.

Par conséquent, je me demande s’il existe un moyen d’exclure une partie des données fréquemment lues (sujets ?) et mises à jour (statistiques utilisateur ?) dans Discourse, par exemple en verrouillant les sujets, ou en attribuant un niveau de confiance 0 aux utilisateurs inactifs, ou quelque chose de ce genre.

Ceci me semble être une bonne piste : vous avez peut-être beaucoup de RAM, mais postgres est configuré pour l’utiliser de certaines manières, et pourrait se compliquer la vie.

Je n’aime pas le fait qu’il y ait deux requêtes UPDATE qui semblent identiques - cela ressemble à une tâche planifiée qui a pris tellement de temps qu’une seconde a été planifiée par la suite. Cela ajoutera une charge dans une situation où les choses ne fonctionnent déjà pas bien.

Mais je ne sais pas comment effectuer ces ajustements.

1 « J'aime »

La dernière fois, nous avons résolu le problème que nous avions en donnant plus d’espace à Postgres - il utilisait trop peu de processus et trop peu de mémoire de travail pour la situation qu’il avait.

Cette fois, je pense que nous avons atteint la limite - soit nous réduisons le nombre de processus et augmentons la mémoire de travail, soit l’inverse, et nous verrons ce qui se passe. Nous avons essayé la première solution il y a quelques heures, nous attendons de voir si cela a un effet bénéfique.

1 « J'aime »

Je serai intéressé de voir ce que vous trouvez. J’ai été administrateur Linux mais pas administrateur de base de données. Il y a énormément de choses à savoir sur les deux aspects.

Dans la documentation de postgres, je trouve

et

Je note que la configuration de Discourse configure quelques paramètres postgres en fonction de la taille de la RAM. Notez que si vous augmentez la taille de votre serveur, ces paramètres ne seront pas ajustés pour la RAM plus importante.

J’ai l’impression qu’une grande partie du savoir et des pratiques datent de l’époque où 2G était un grand serveur. Il est tout à fait possible que des nombres plus élevés soient appropriés pour le matériel d’aujourd’hui.

Edit : sur mes serveurs (4G), je désactive les pages volumineuses (huge pages). Je crois que les pages volumineuses transparentes (automatiques) peuvent amener le noyau à passer du temps à fusionner et à diviser. Mais je vois dans la documentation postgres que les pages volumineuses peuvent être bénéfiques dans certaines situations.

Sur mon système :

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:~# 

À moins que les choses n’aient changé, l’exécution de ./discourse-setup devrait ajuster les chiffres mais détecter qu’il s’agit d’une installation déjà en cours d’exécution. Si je me souviens bien, il sauvegarde également le fichier app.yml avant d’apporter des modifications.

Je ne sais pas si cela a changé récemment car la dernière fois que je l’ai fait, c’était il y a 3 ou 4 ans.

Autant que je me souvienne, cela ne modifie que des choses comme la mémoire partagée (25 % de la mémoire maximale) et les travailleurs web unicorn. Je pourrais oublier quelque chose.

(pas dans le swap, mais elles lisent depuis le disque)

ceci correspond à mes observations précédentes :

Notez que c’est la taille de la relation et des index. Comparez avec pg_relation_size.

Ceci provient de ScoreCalculator, faisant partie de PeriodicalUpdates.

C’est votre constat qui doit être résolu. En comparaison, ici sur meta, Jobs::EnsureDbConsistency prend moins de 2 minutes et Jobs::TopRefreshOlder prend moins de 10 secondes :

Postgres a besoin de plus de mémoire. Donnez-lui autant que vous le pouvez.

Vous pourriez également constater un bénéfice avec un VACUUM ANALYZE ou VACUUM ANALYZE FULL. Effectuer le premier ne fait jamais de mal.

Je ferais probablement, dans l’ordre :

  • vacuum analyze
  • mettre en pause sidekiq puis vacuum analyze full (cela gèle les tables pour les réécrire entièrement, peut entraîner des échecs pendant son exécution)
  • plus de mémoire pour postgres
1 « J'aime »

J’ai eu quelques restaurations/mises à niveau échouer pendant la migration à cause de l’espace, malgré plusieurs Go d’espace restant (comme Restore fails due to disk space on migration).

Est-ce qu’effectuer un vacuum avant la sauvegarde pourrait aider avec ce problème ?

Aussi

C’est

 discourse=# VACUUM FULL ANALYZE;

Bon conseil à ce sujet, je pense :