대규모 미확인 사용자 알림으로 인해 대형 큐에서 발생하는 매우 느린 Sidekiq 문제

감사합니다 @Falco

주로 동일한 구성으로 하루에 약 11만 건에서 약 30만 건의 작업을 처리하는 성능이 약 1주일 사이에 어떻게 이렇게 달라질 수 있는지(초당 처리 작업 수 기준 약 35배 차이)가 궁금합니다.

CPU 사용률은 다시 약 15~20%로 내려왔는데, 이는 평소 수준입니다. 작업 처리 속도도 여전히 느린 상태입니다.

확인 차원에서 말씀드리자면, 저는 일부 Sidekiq 프로세스를 낮은 우선순위 큐를 전담하도록 할당(assign)하자는 의미였지, 단순히 추가(add)하자는 뜻은 아니었습니다. 낮은 우선순위 작업은 훨씬 빠른 속도로 처리될 수 있고, 동일한 병목 현상을 겪지 않을 것으로 보였기 때문입니다. 초당 처리 작업 수가 이렇게 극단적으로 달라지는 이유(즉, 기본 큐의 대기열에 막힌 낮은 우선순위의 ‘쉬운’ 작업들)를 설명할 수 있을 것이라 추측했습니다.

정리하자면, 작업 완료 속도가 느린 것이 PostgreSQL 성능 때문이라고 보시는지, 아니면 어제 관찰했던 높은 CPU 사용률 이벤트(현재는 정상으로 돌아왔습니다) 때문이라고 보시는지 궁금합니다.

이 모든 게 SSD에 있는 거죠?

네, 맞습니다 @Stephen - NVMe SSD RAID 1입니다.

업데이트: 저우선순위 큐와 기본 큐를 몇 번 삭제해 보았지만, 기본 큐가 즉시 다시 채워지기 때문에 속도에 아무런 영향이 없었습니다. 그 다음 기본 큐를 삭제하고 읽기 전용 모드를 활성화해 보았습니다. 이 방법으로 초당 작업 처리량이 급격히 증가하여 저우선순위 큐를 매우 빠르게 처리했습니다(초당 약 100배의 속도).

수정: 단순히 저우선순위 큐가 크더라도 처리 속도는 여전히 느린 것으로 보입니다. Discourse를 읽기 전용으로 설정한 후 저우선순위와 기본우선순위 큐를 모두 비우면, 읽기 전용 모드를 해제할 때까지 예약된 작업과 큐가 매우 빠르게 비워지는 것처럼 보입니다. :yuno:

다음 단계는 Discourse 앱에 들어가서 htop이나 top을 실행하여 CPU 사용량이 가장 높은 프로세스를 확인하고, 정확히 어떤 프로세스가 문제를 일으키고 있는지 파악하는 것입니다.

Postgres가 병목 현상의 원인인 것 같습니다. Prometheus를 설정하여 성능을 추적하고, 충분한 RAM에 접근하고 있는지 확인해 볼 수 있습니다.

입력해 주셔서 감사합니다 @pfaffman :slight_smile: app.yml의 db_shared_buffers와 db_work_mem가 PostgreSQL의 RAM 접근을 제어하는 유일한 옵션이 맞나요?

위쪽으로도 아래쪽으로도 조금씩 조정해 봤습니다. 현재 app.yml의 설정은 다음과 같습니다:
db_shared_buffers: “32768MB”
db_work_mem: “128MB”

시스템 총 RAM은 128GB입니다.

또한 /var/discourse/shared/standalone/postgres_data/postgresql.conf에서 max_connections를 변경한 후 Discourse를 다시 빌드해 보기도 했습니다. 기본값(100)보다 높은 값인 200에서 500까지 시도해 보았으며, 현재는 300으로 설정되어 있습니다. 다만 해당 파일에서 수정한 것이 실제로 최대 연결 수 값을 변경하는 것인지 확실하지 않습니다.

/var/discourse/templates/postgres.template.yml 파일에서 다음 내용을 확인했습니다.

db_synchronous_commit: “off”
db_shared_buffers: “256MB”
db_work_mem: “10MB”
db_default_text_search_config: “pg_catalog.english”
db_name: discourse
db_user: discourse
db_wal_level: minimal
db_max_wal_senders: 0
db_checkpoint_segments: 6
db_logging_collector: off
db_log_min_duration_statement: 100

@bartv 님, 조언해 주신 대로 Discourse 앱 안에서 top을 통해 모니터링하고 있습니다. postgres 사용자가 실행하는 postmaster 프로세스가 상당히 많이 표시되고 있으며, CPU 사용량은 변동이 있습니다. 아래 스크린샷들은 비슷한 사용량 통계가 장기간 지속되는 상황을 나타냅니다.

32개 코어 중 약 95% 사용:

약 20% 사용, postmaster의 CPU 사용량이 낮은 상태.

읽기 전용(Read-only) 모드가 활성화되어 있을 때 약 6% CPU 사용.

데이터베이스는 얼마나 큰가요? 사용자가 몇 명이나 되나요? 하루에 새로운 게시글이 얼마나 올라오나요?

가장 먼저 해야 할 일은 postgres 콘솔에서 VACUUM ANALYZE;를 실행하는 것입니다.

이 작업에는 시간이 좀 걸릴 수 있으므로, 처리 중 부하를 줄이기 위해 sidekiq을 일시적으로 중지하는 것이 좋습니다.

그것이 도움이 되지 않으면 pg_stat_statements를 활성화한 후 CPU를 많이 사용하는 쿼리가 무엇인지 확인해야 합니다.

@pfaffman

  • /var/discourse/shared/standalone/postgres_data 폴더가 170GB입니다
  • 지난 30일 동안 활성 사용자가 61.7k명입니다 (절대적 총 수는 불분명합니다)
  • 하루에 약 5만~8만 개의 새 게시물이 생성됩니다

오, 그건 쉽지 않은 문제네요.

포스트그레SQL 튜닝에 대해 공부해 보시는 게 좋을 것 같아요. 여기서 일반적으로 볼 수 있는 셀프호스팅 수준으로는 그 정도 성능을 내기엔 다소 한계가 있거든요.

저라면 포스트그레SQL에 전체 RAM의 약 4분의 3을 할당해 볼 거예요. 데이터 컨테이너와 웹 컨테이너를 반드시 분리해서 사용해야 합니다. 하지만 필요한 성능을 달성하려면 좀 더 복잡한 포스트그레SQL 설정이 필요할 수도 있어요.

수정: 하지만 저는 훨씬 더 큰 규모의 데이터베이스에 대한 경험이 많지 않으니, 아래 내용을 참고해 주세요! :wink:

우리는 훨씬 더 큰 데이터베이스를 훨씬 적은 RAM으로, 그리고 CPU 사용량도 거의 없이 처리합니다.

pg_stat_statements 정보는 문제의 원인을 파악하는 데 도움이 될 것입니다.

도와주셔서 정말 감사합니다.

VACUUM ANALYZE;를 실행해 보았지만, 안타깝게도 효과가 없었습니다. 참고용으로 아래에 사용된 명령어를 나열합니다:

cd /var/discourse/
./launcher enter app
sudo -u postgres psql
\c discourse
VACUUM ANALYZE;

pg_stat_statements를 활성화하려고 시도했으며, 아래 단계들을 수행했습니다:

/var/discourse/shared/standalone/postgres_data/postgresql.conf 파일에 아래 줄들을 추가/수정했습니다:

shared_preload_libraries = ‘pg_stat_statements’ # (변경 시 재시작 필요)
pg_stat_statements.max = 10000
pg_stat_statements.track = all
pg_stat_statements.track_utility = on
pg_stat_statements.save = off

그 후 Discourse를 다시 빌드하고 다음을 실행했습니다:

./launcher enter app
sudo -u postgres psql
\c discourse
CREATE EXTENSION pg_stat_statements:

쿼리를 실행해 보았지만, 다음과 같은 오류가 발생합니다:

ERROR: pg_stat_statements must be loaded via shared_preload_libraries

제 추측으로는 postgresql.conf 파일(/var/discourse/shared/standalone/postgres_data/postgresql.conf)에 대한 제 수정 사항이 제대로 적용되지 않고 있는 것 같습니다 (편집 후 Discourse를 다시 빌드했습니다). app.yml 파일을 통해 이러한 설정을 수정할 수 있을까요? 아니면 제가 잘못한 부분이 있는지 확인해 주실 수 있을까요?

Discourse를 다시 빌드하면 해당 변경 사항이 삭제됩니다. 컨테이너를 재시작하면 해결될 가능성이 높습니다. (컨테이너 내부에서 sv restart postgres와 같은 명령을 실행해도 작동할 수 있습니다).

감사합니다. 컨테이너를 재시작해 보았습니다:

./launcher stop app
./launcher start app

쿼리를 시도할 때 여전히 동일한 오류가 발생합니다:

ERROR: pg_stat_statements must be loaded via shared_preload_libraries

이전에 수행했던 변경 사항들은 재빌드 후에도 해당 파일에 여전히 유지되고 있습니다:

/var/discourse/shared/standalone/postgres_data/postgresql.conf

이 파일이 이러한 편집을 수행해야 할 올바른 위치가 아닌 것 같습니다 :face_with_monocle:

컨테이너 폴더에서 app.yml(그리고 mail-receiver.yml)만 사용하고 data.yml을 구현하지 않았기 때문일 수 있습니다.

실제로 확인하지는 않았지만, 컨테이너 내부의 /etc/postgres일 가능성이 높습니다. 또한 필요한 라이브러리를 설치해야 할 수도 있습니다.

감사합니다. 처음에는 정말 큰 도움이 되었습니다. :man_cartwheeling: 문제가 해결된 것처럼 보였고, postgresql.conf에서 최대 연결 수만 늘렸을 뿐인데 작업 큐 처리 속도가 정말 빨라졌습니다. 하지만 약 하루 정도 지나자 다시 느려졌습니다.

postgresql의 max_connections를 늘리고자 하는 다른 분들에게 도움이 될 수 있도록 아래에 단계를 정리해 두었습니다.

docker ps

컨테이너 ID(예: aaabbbccc123)를 확인하고, 아래 명령어에서 해당 ID로 치환하세요:

Docker 컨테이너 내부의 postgresql.conf 파일을 로컬 파일 시스템으로 복사합니다:

docker cp aaabbbccc123:/etc/postgresql/10/main/postgresql.conf /srv

설정 파일을 편집합니다:

nano /srv/postgresql.conf

파일을 Docker 컨테이너 내부로 다시 복사합니다:

docker cp /srv/postgresql.conf aaabbbccc123:/etc/postgresql/10/main/postgresql.conf

cd /var/discourse
./launcher stop app
./launcher start app

남은 파일을 삭제합니다 (선택 사항):

rm /srv/postgresql.conf

@supermathie pg_stat_statements를 성공적으로 활성화한 것 같습니다 :grinning:

다음 쿼리를 시도해 보았습니다:

SELECT
(total_time / 1000 / 60) as total,
(total_time/calls) as avg,
query
FROM pg_stat_statements
ORDER BY 1 DESC
LIMIT 100;

다음 가이드에서 가져왔습니다: The most useful Postgres extension: pg_stat_statements - Citus Data

하지만 결과를 제대로 읽을 수 없습니다. 뭔가 잘못한 것 같습니다.

total | avg |

무엇을 하고 싶은지 알게 되셨다면, 이제 app.yml 파일의 replace 스탠사를 사용하여 이러한 변경 사항을 적용할 수 있습니다.

또는 ./launcher enter app 명령을 실행하여 파일을 직접 편집할 수도 있습니다. 다만, 재빌드 시 이러한 변경 사항은 새 컨테이너에 반영되지 않는다는 점에 유의하세요.