Uso de CPU inusualmente alto

Un último dato, y luego creo que me desconectaré por unas horas.

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)

Mi pregunta, básicamente, se reduce a “¿qué podría crear una consulta UPDATE que se quede colgada durante 9 horas?”.
Hipotesizaría que no hay suficiente memoria: las consultas van al swap.
¿Ser una tabla de posts de 40 GB es un problema potencial?


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

¿Has intentado ajustar la configuración de memoria de postgres?

Si migrar a una nueva máquina virtual solucionara algo, no es un gran problema y se puede hacer sin tiempo de inactividad y solo con un poco de tiempo de solo lectura.

1 me gusta

Sí, ya lo hicimos una vez porque tuvimos un problema algo similar: era claramente Postgres quedándose sin memoria.

Esta vez, los síntomas son diferentes; esto no significa que estemos excluyendo la posibilidad de tener un problema similar, sin embargo.

Ahora mismo, muchas pistas apuntan a que algunas estructuras de hilos están siendo demasiado pesadas para las tareas internas de Discourse: esta instalación proviene de una conversión DIY desde un foro vbb, y debo reiterar que tuvimos prácticamente ningún problema durante 2 años seguidos, luego el año pasado tuvimos que ajustar la configuración de postgres, después navegación tranquila hasta hace unos 10 días, y luego de nuevo después de unos días de la última actualización de versión.

El punto es que tenemos muchos hilos antiguos que Discourse no ha dividido en fragmentos de 5000 publicaciones, tenemos miles de cuentas, y estoy empezando a pensar que el historial de publicaciones importado anteriormente más el uso normal ha alcanzado un umbral donde nuestro hardware y la arquitectura de Discourse están luchando para manejar las operaciones normales.

Entonces, otra de las preguntas que tengo ahora es que hace mucho tiempo noté que Discourse es un software de foro ampliamente utilizado para grandes soluciones comerciales (es decir, Activision Blizzard). Entiendo que esas son instalaciones pagas y que el equipo de Discourse recibe un pago para ofrecer soporte adecuado, y existe la opción de arrojar dinero al problema, pero no puedo evitar preguntarme qué tan grande puede ser su tabla de publicaciones, y no creo que sea más pequeña que la nuestra. Aún así, me sorprendería si una solución autohospedada tuviera problemas con el uso activo que tenemos (~150 usuarios activos, ~2k publicaciones nuevas por día, más o menos).

Además, la noche que pusimos el foro en modo de solo lectura, el foro era rapidísimo. Evidentemente, la actividad del usuario, que incluye publicar pero va más allá, está causando la situación que tenemos.

Por lo tanto, me pregunto si hay alguna manera de excluir parte de los datos leídos con frecuencia (¿temas?) y actualizados (¿estadísticas de usuario?) en Discourse, por ejemplo, bloqueando temas, o asignando un nivel de confianza 0 a usuarios inactivos, o algo por el estilo.

Esta me parece una buena línea de investigación: es posible que tengas mucha RAM, pero postgres está configurado para usarla de ciertas maneras, y podría estar dificultándose las cosas a sí mismo.

No me gusta que haya dos consultas UPDATE que parecen idénticas; parece que podría ser una tarea programada que tardó tanto que se programó una segunda posteriormente. Estará añadiendo carga en una situación en la que las cosas ya no funcionan bien.

Pero no sé cómo hacer esos ajustes.

1 me gusta

La última vez resolvimos el problema que teníamos dándole a Postgres más espacio: estaba utilizando muy pocos procesos y muy poca memoria de trabajo para la situación que tenía.

Esta vez creo que hemos tocado techo: o reducimos el número de procesos y aumentamos la memoria de trabajo, o al revés, y vemos qué pasa. Intentamos la primera solución hace unas horas, estamos esperando a ver si está teniendo algún efecto beneficioso.

1 me gusta

Me interesará ver lo que encuentras. He sido administrador de Linux, pero no administrador de bases de datos. Hay una gran cantidad de cosas que saber sobre ambos lados.

En la documentación de postgres encuentro

y

Observo que la configuración de Discourse configura un par de parámetros de postgres de acuerdo con el tamaño de la RAM. Ten en cuenta que si aumentas el tamaño de tu servidor, esos parámetros no se ajustarán a la RAM mayor.

Siento que gran parte de la tradición y la práctica pueden datar de la época en que 2G era un servidor grande. Es muy posible que números más grandes sean apropiados para el hardware actual.

Edición: en mis servidores (de 4G) desactivo las páginas grandes (huge pages). Creo que las páginas grandes transparentes (automáticas) pueden hacer que el kernel dedique tiempo a fusionar y dividir. Pero veo en la documentación de postgres que las páginas grandes pueden ser beneficiosas en algunas situaciones.

En mi sistema:

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

A menos que las cosas hayan cambiado, ejecutar ./discourse-setup debería ajustar los números pero detectar que es una instalación que ya se está ejecutando. Si no recuerdo mal, también hace una copia de seguridad del archivo app.yml antes de realizar cambios.

No sé si esto ha cambiado recientemente, ya que la última vez que lo hice fue hace 3 o 4 años.

Hasta donde recuerdo, solo cambia cosas como la memoria compartida (25% de la memoria máxima) y los trabajadores web de unicornio. Puede que esté olvidando algo.

(no al swap, sino que están leyendo desde el disco)

Esto coincide con mis observaciones anteriores:

Ten en cuenta que este es el tamaño de la relación e índices. Compáralo con pg_relation_size.

Esto es de ScoreCalculator, parte de PeriodicalUpdates.

Este es tu hallazgo que necesita ser resuelto. En comparación, aquí en meta Jobs::EnsureDbConsistency tarda <2min y Jobs::TopRefreshOlder tarda <10s:

Postgres necesita más memoria. Dale tanta como puedas.

También podrías ver un beneficio con un VACUUM ANALYZE o VACUUM ANALYZE FULL. Hacer el primero nunca perjudica.

Yo probablemente haría, en orden:

  • vacuum analyze
  • pausar sidekiq y luego vacuum analyze full (esto congela las tablas para reescribirlas completamente, puede generar algunos fallos mientras se ejecuta)
  • más memoria para postgres
1 me gusta

He tenido un par de restauraciones/actualizaciones fallidas durante la migración debido a falta de espacio, a pesar de que quedaban varios GB de espacio (como Restore fails due to disk space on migration).

¿Podría ayudar a solucionar ese problema hacer un vacuum antes de la copia de seguridad?

Además

Eso es

 discourse=# VACUUM FULL ANALYZE;

Aquí hay un buen consejo sobre eso, creo: