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)

제 질문은 결국 "9시간 동안 멈추어 있는 UPDATE 쿼리를 생성할 수 있는 것은 무엇인가?

Postgres의 메모리 설정을 조정해 보셨나요?

새 VM으로 전환하는 것이 문제를 해결할 수 있다면, 큰 문제가 아니며 다운타임 없이, 읽기 전용 시간만 약간 소요되어 처리할 수 있습니다.

네, 비슷한 문제가 있어서 이전에 한 번 해본 적이 있습니다. 그때는 분명히 Postgres가 메모리를 소진한 것이었죠.

이번에는 증상이 다르지만, 그렇다고 해서 비슷한 문제가 발생할 가능성을 배제하는 것은 아닙니다.

현재 많은 단서들이 Discourse의 내부 작업을 위해 스레드 구조가 너무 무겁다는 것을 가리키고 있습니다. 이 설치 환경은 vbb 포럼에서 DIY 방식으로 전환한 것입니다. 그리고 다시 한번 말씀드리자면, 우리는 2년 동안 거의 문제가 없었고, 지난해에는 Postgres 설정을 조정해야 했으며, 그 후로 지난 10일 전까지 순조롭게 운영되었습니다. 그리고 마지막 버전 업그레이드 후 며칠 지나자 다시 문제가 발생했습니다.

핵심은 Discourse가 5,000개 게시글 단위로 분리하지 않은 오래된 스레드가 많고, 수천 개의 계정이 있으며, 이전에는 문제가 없었던 이전의 가져온 게시글 기록과 일반적인 사용량이 우리의 하드웨어와 Discourse 아키텍처가 정상적인 작업을 처리하기 어려울 만큼 임계값에 도달했다는 생각을 하기 시작했다는 것입니다.

따라서 제가 지금 가지고 있는 또 다른 질문은, Discourse가 대형 상업용 솔루션(예: Activision Blizzard)에 널리 사용되고 있다는 것을 오래전부터 인지하고 있다는 것입니다. 이러한 것은 유료 설치 환경이며, Discourse 팀이 적절한 지원을 제공하기 위해 비용을 받는 것임을 이해하고 있습니다. 또한 문제 해결에 돈을 쏟아부을 수 있는 옵션도 있지만, 그들의 posts 테이블이 얼마나 클 수 있는지, 그리고 그것이 우리의 것보다 작을 수 없다고 생각합니다. 그럼에도 불구하고, 우리가 가진 활성 사용량(활성 사용자 약 150명, 하루 약 2,000개의 새 게시글)으로 인해 셀프호스팅 솔루션에 문제가 발생할 것이라고 생각하면 놀라울 것입니다.

또한, 포럼을 읽기 전용 모드로 전환한 그 밤에는 포럼이 매우 빠르게 작동했습니다. 분명히 게시글 작성뿐만 아니라 그 이상의 것을 포함하는 사용자 활동이 우리가 직면한 상황을 유발하고 있습니다.

따라서 Discourse에서 자주 읽히고(주제?) 업데이트되는(사용자 통계?) 데이터의 일부, 예를 들어 주제를 잠그거나 비활성 사용자에게 신뢰 수준 0을 지정하는 것 등을 통해 제외할 방법이 있는지 궁금합니다.

저에게는 좋은 질문 방향인 것 같습니다. RAM 용량이 많더라도, 포스트그레스가 메모리를 특정 방식으로 사용하도록 설정되어 있어 스스로에게 부담을 주고 있을 수 있습니다.

완전히 동일한 것처럼 보이는 UPDATE 쿼리가 두 개 있다는 점은 마음에 들지 않습니다. 이는 스케줄된 작업이 너무 오래 걸려서 두 번째 작업이 추가로 예약된 것처럼 보일 수 있습니다. 시스템이 이미 원활하지 않은 상황에서 부하가 더 증가하게 됩니다.

하지만 해당 설정을 어떻게 조정해야 하는지는 잘 모르겠습니다.

지난번에는 Postgres에 더 많은 공간을 할당하여 문제를 해결했습니다. 당시 해당 환경에는 프로세스 수가 너무 적고 워킹 메모리가 부족했기 때문입니다.

이번에는 한계에 도달한 것 같습니다. 프로세스 수를 줄이고 워킹 메모리를 늘리거나, 그 반대인 워킹 메모리를 줄이고 프로세스 수를 늘려서 어떤 결과가 나올지 확인해 볼 계획입니다.
몇 시간 전에 첫 번째 해결책을 시도해 보았으며, 이것이 긍정적인 효과를 가져올지 지켜보고 있습니다.

무엇을 발견하게 될지 궁금합니다. 저는 리눅스 관리자로서는 경력이 있지만, 데이터베이스 관리자로서는 아닙니다. 양쪽 모두 알아야 할 것이 매우 많습니다.

Postgres 문서에서 다음을 찾았습니다.

그리고

Discourse 설정이 RAM 크기에 따라 몇 가지 Postgres 매개변수를 구성한다는 점을 주목했습니다. 서버를 업스케일링(업그레이드)하면 더 큰 RAM에 맞게 이러한 매개변수가 조정되지 않는다는 점에 유의하십시오.

2GB가 큰 서버였던 시절부터 이어져 온 많은 경험과 관행이 있을 수 있다고 느낍니다. 오늘날의 하드웨어에는 더 큰 숫자가 적합할 가능성이 충분히 있습니다.

수정: 제 (4GB) 서버에서는 huge pages를 비활성화합니다. 투명(자동) huge pages가 커널이 병합 및 분할에 시간을 소비하게 만들 수 있다고 생각합니다. 하지만 Postgres 문서에서 huge pages가 특정 상황에서 이점이 될 수 있다는 것을 확인했습니다.

제 시스템에서는:

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) 웹 워커 수만 변경하는 것 같습니다. 제가 놓친 부분이 있을 수도 있습니다.

(스왑이 아니라 디스크에서 읽는 것입니다)

이것은 저의 이전 관찰과 일치합니다:

이것은 관계(relation) 및 인덱스의 크기입니다. pg_relation_size와 비교해 보세요.

이것은 PeriodicalUpdates의 일부인 ScoreCalculator에서 나온 것입니다.

해결해야 할 이 발견 사항입니다. 비교하자면, 여기 메타(meta)에서는 Jobs::EnsureDbConsistency가 2분 미만, Jobs::TopRefreshOlder가 10초 미만입니다:

Postgres에는 더 많은 메모리가 필요합니다. 가능한 한 많이 할당해 주세요.

VACUUM ANALYZE 또는 VACUUM ANALYZE FULL 실행에서도 이점을 볼 수 있습니다. 전자는 실행해도 해가 되지 않습니다.

아래 순서대로 진행하는 것이 좋습니다:

  • vacuum analyze
  • sidekiq를 일시 정지한 후 vacuum analyze full 실행 (이 작업은 테이블을 완전히 다시 작성하기 위해 테이블을 잠금(lock)하므로, 실행 중 일부 실패가 발생할 수 있음)
  • postgres에 더 많은 메모리 할당

여러 GB의 공간이 남아 있음에도 불구하고 공간 부족으로 인해 마이그레이션 중 복원/업그레이드가 실패한 사례가 몇 번 있었습니다 (예: Restore fails due to disk space on migration because of 70M calendar events).

백업 전에 vacuum을 실행하면 이 문제를 해결하는 데 도움이 될까요?

또한

이것은

 discourse=# VACUUM FULL ANALYZE;

여기에 대한 좋은 조언이 있다고 생각합니다:
https://stackoverflow.com/a/69206920

아마도 그렇지 않을 것 같습니다. PostgreSQL은 파일 안의 빈 공간(holes)을 언매핑(unmap)하기 때문이죠. 하지만 도움이 될 수도 있습니다. 해로운 일은 아닐 테니, 시도해 보고 결과를 보고하면 될 것 같습니다 :+1:

추가 업데이트를 공유합니다.

진공 청소(데이터 정제)와 진공 루틴 설정이 효과를 보였습니다. 리소스 사용량의 피크는 이전과 같은 "높이"를 유지하고 있지만, 적어도 지난 달만큼 "넓지"는 않습니다.
이것은 상황을 근본적으로 해결하는 것은 아닙니다.

테이블 구조의 대규모 부분을 포함하는 작업(예: 사용자 프로필 접근 또는 게시물 이동)은 때때로 502 오류로 이어집니다.

현재 포럼 구조를 유지하기를 원하기 때문에, 가능한 한 빨리 전용 머신으로 이전할 예정입니다.

지금까지의 도움에 감사드립니다.