异常高的 CPU 使用率

最后一点信息,然后我想我要下线几个小时了。

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 个小时?”

我猜是没有足够的内存:查询进入了交换空间(swap)。
一个 40GB 的 posts 表会是一个潜在问题吗?


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

您尝试调整过 postgres 的内存设置吗?

如果迁移到新的虚拟机能解决任何问题,那也不是什么大问题,可以在零停机时间和一点点只读时间的情况下完成。

1 个赞

是的,我们之前已经做过一次,因为我们遇到了一个类似的问题——这明显是 PostgreSQL 内存耗尽了。

这次的症状不同——但这并不意味着我们排除了出现类似问题的可能性。

目前很多迹象都表明是某些线程结构对于 Discourse 的内部任务来说太“重”了:这个安装是通过从 vbb 论坛 DIY 转换而来的——我必须再次声明,我们有将近两年的时间里几乎没有问题,然后去年我们不得不调整 postgresql 的设置,之后一切顺利,直到大约 10 天前,然后在上次版本升级几天后又出现了问题。

关键在于我们有许多 Discourse 尚未拆分成 5000 帖块的古老帖子,我们有数千个账户,我开始认为之前导入的帖子历史记录加上正常的日常使用已经达到了一个阈值,我们的硬件和 Discourse 架构正在努力应对正常的运行。

所以,我现在有的另一个疑问是,我早就注意到 Discourse 是一个被广泛用于大型商业解决方案(例如 Activision Blizzard)的论坛软件。我理解这些是付费安装,Discourse 团队会因提供适当支持而获得报酬,并且可以选择“花钱解决问题”,但我忍不住想知道他们的帖子表能有多大,我不认为会比我们的要小。然而,如果一个自托管解决方案在我们目前的活跃使用量(约 150 个活跃用户,每天约 2k 新帖子,或多或少)下出现问题,我会感到惊讶。

另外,我们把论坛设为只读模式的那个晚上,论坛运行速度非常快。很明显,用户活动(包括发帖,但不仅仅是发帖)是导致我们当前状况的原因。

因此,我想知道是否有办法排除 Discourse 中经常读取(主题?)和更新(用户统计数据?)的部分数据,例如锁定主题,或者为不活跃用户分配信任等级 0,或者类似的操作。

我觉得这是一个很好的问题方向:你可能有大量的内存,但 postgres 的配置方式决定了它如何使用这些内存,这可能会让它自己处境艰难。

我不喜欢有两个看起来完全相同的 UPDATE 查询——这看起来像是一个计划任务耗时太久,导致第二个任务随后被调度执行了。这会在本已运行不佳的情况下增加负载。

但我不知道如何进行那些调整。

1 个赞

上次我们通过给 Postgres 更多空间解决了问题——它使用的进程太少,工作内存也不足以应对当时的情况。

这一次,我认为我们已经达到了极限——我们要么减少进程数量并增加工作内存,要么反过来,然后看看会发生什么。我们几个小时前尝试了第一个解决方案,我们正在等待看看这是否会产生任何有益的影响。

1 个赞

我很期待看到你的发现。我曾是一名 Linux 管理员,但不是数据库管理员。这两方面都有很多知识需要学习。

在 PostgreSQL 文档中,我找到了

我注意到 Discourse 的设置会根据 RAM 大小配置几个 PostgreSQL 参数。请注意,如果你升级你的服务器,这些参数不会针对更大的内存进行调整。

我觉得很多知识和实践可能源于 2G 还是大服务器的时代。很有可能对于今天的硬件来说,更大的数值是合适的。

编辑:在我的(4G)服务器上,我禁用了大页(huge pages)。我相信透明(自动)大页可能会导致内核花费时间进行合并和拆分。但我看到 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 文件。

我不知道这是否是最近才改变的,因为我上次做这件事是在三四年前。

据我回忆,它只是更改了像共享内存(最大内存的 25%)和 unicorn Web 工作进程之类的东西。我可能忘记了什么。

(不是交换空间,但它们正在从磁盘读取)

这与我早先的观察结果一致:

请注意,这是关系和索引的大小。与 pg_relation_size 进行比较。

这来自 ScoreCalculator,它是 PeriodicalUpdates 的一部分。

这是您需要解决的发现。相比之下,在 meta 上 Jobs::EnsureDbConsistency 耗时不到 2 分钟,而 Jobs::TopRefreshOlder 耗时不到 10 秒:

Postgres 需要更多的内存。尽可能多地分配给它。

您也可能会从 VACUUM ANALYZEVACUUM ANALYZE FULL 中受益。执行前者永远没有坏处。

我可能会按顺序执行以下操作:

  • vacuum analyze
  • 暂停 sidekiq 然后 vacuum analyze full(这将冻结表以完全重写它们,运行期间可能会出现一些失败)
  • 为 postgres 增加更多内存
1 个赞