70M 캘린더 이벤트로 인해 디스크 공간 부족으로 마이그레이션 중 복원 실패

표준 설치 환경에서 데이터베이스를 복원하려고 하고 있습니다. 마이그레이션 단계에서 실패하고 있습니다.


ALTER TABLE
Migrating the database...
EXCEPTION: rake db:migrate
Failed to migrate database.
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled: (StandardError)

PG::DiskFull: ERROR:  could not write to file "base/pgsql_tmp/pgsql_tmp11009.51": No space left on device
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb:109:in `exec'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb:109:in `async_exec'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-8.0.4/lib/active_record/connection_adapters/postgresql/database_state
ments.rb:167:in `perform_query'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-8.0.4/lib/active_record/connection_adapters/abstract/database_stateme
nts.rb:556:in `block (2 levels) in raw_execute'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-8.0.4/lib/active_record/connection_adapters/abstract_adapter.rb:1017:
in `block in with_raw_connection'

디스크에는 90GB의 여유 공간이 있습니다. 소스 디스크의 postgres 디렉토리는 단 23GB뿐입니다.

90GB의 여유 공간이 있는데, 23GB인 데이터베이스가 왜 데이터베이스 마이그레이션 중 복원에 실패하는 것일까요?

소스 – 서버 버전: 3.5.0.beta5-dev (커밋: b16fb6a60b3f1db475cbb91a51b7d4c734370083 — 2025년 5월 7일)

대상 서버 버전: 2026.2.0-latest (커밋: b39866eb8891648a54764755e2e36eb725bd6c73 — 4일 전)

23G     /var/discourse/shared/standalone/postgres_data/
-rw-r--r-- 1 discourse discourse  16G Feb 10 21:13 site-2026-02-10-174058-v20250507013646.sql
-rw-r--r-- 1 discourse discourse 2.9G Feb 10 21:11 site-2026-02-10-174058-v20250507013646.sql.gz
root@forum-data:/shared# # after delete
root@forum-data:/shared# du -hs postgres_data/; df -h .
24G     postgres_data/
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G   87G   68G  56% /shared
....
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G  154G  607M 100% /shared
overlay         154G  154G  607M 100% /
91G     postgres_data/
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G  154G   82M 100% /shared
overlay         154G  154G   82M 100% /
1.1G    postgres_data/
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G   65G   90G  42% /shared
overlay         154G   65G   90G  42% /
1.1G    postgres_data/
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G   65G   90G  42% /shared
overlay         154G   65G   90G  42% /
1.1G    postgres_data/
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G   65G   90G  42% /shared
overlay         154G   65G   90G  42% /
1.1G    postgres_data/
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1       154G   65G   90G  42% /shared
overlay         154G   65G   90G  42% /
1.1G    postgres_data/

데이터베이스가 복원된 후 postgres_data는 24GB였습니다.

마이그레이션 과정에서 일부 요인으로 인해 디스크가 가득 차기 전까지 postgres_data가 91GB까지 팽창하여 복원이 실패했습니다. 이는 위와 같은 Discourse 버전의 클린 설치 환경에서 발생한 문제입니다.

이는 분명히 버그인 것 같아 카테고리를 변경합니다. 업그레이드를 시도할 때도 같은 현상을 확인했습니다. 해당 업그레이드를 위해 새 서버로 이전하는 것이 해결책일 것이라고 생각했지만, 이제 그것이 불가능하다는 것을 알게 되었습니다.

다음 단계로 이 문제를 일으키는 쿼리와/또는 테이블이 무엇인지 확인해 볼 예정입니다. 정확히 어떻게 접근해야 할지 아직 잘 모르겠습니다.

이 메시지를 지원팀에 전달합니다. 이 메시지는 파일 시스템에서 직접 전송된 것입니다. PG가 공간이 없다고 판단한다면, 실제로는 해당 영역에 할당된 공간이 부족하기 때문일 가능성이 높습니다.

데이터베이스 마이그레이션 중 24GB 데이터베이스가 90GB까지 확장될 수 있는 원인에 대한 힌트가 있으신가요?

확신은 없는데… 아마도 ncdu나 비슷한 도구를 실행해서 해당 상황이 발생했을 때를 확인해 볼 것 같습니다.

dudf를 사용하여 postgres_data의 크기가 25GB에서 90GB로 증가하는 것을 관찰했고, 디스크 공간이 (거의) 0에 가까워진 후 실패한 것을 확인했습니다.

다음에는 어떤 쿼리가 실행 중인지 추적하는 방법을 찾아야 할 것 같습니다.

최소 두 곳에서 이 문제를 목격했습니다. 하나는 업그레이드 중이었고, 다른 하나는 복원 중이었습니다. 두 경우 모두 시작 시 데이터베이스 크기보다 여유 공간이 더 많았습니다.

마이그레이션 중 일부가 테이블 전체를 다시 작성하며, 이로 인해 일시적으로 더 많은 공간이 사용될 수 있습니다. 또한 PostgreSQL은 시스템에 공간을 적극적으로 반환하지 않습니다.

해당 포럼에 임베딩이 활성화된 AI가 있나요?

저도 그런 생각을 하고 있었습니다. . .

그것이 유력한 원인으로 보입니다. . . . 아쉬워. 아니요. AI 플러그인조차 설치되어 있지 않습니다.

토끼굴에 빠져들고 싶다면, 2025년 5월 커밋 상태로 돌아가는 개발 환경을 수동으로 복원한 후, 현재 커밋으로 이동하여 마이그레이션을 하나씩 실행하면서 각 단계 전후의 공간을 출력해 볼 수 있습니다 — 당연히 스크립트를 이용하면서요 :stuck_out_tongue:.

OK. 저는 프로덕션 사이트에서 복원 작업 중입니다. 마이그레이션이 진행되고 있습니다.

30분 동안 실행 중인 쿼리가 있습니다:

discourse=# SELECT pid, usename, state, query, query_start
FROM pg_stat_activity
WHERE state = 'active';
 pid |  usename  | state  |                                                    query                                                     |          query_start
-----+-----------+--------+--------------------------------------------------------------------------------------------------------------+-------------------------------
 519 | postgres  | active | SELECT pid, usename, state, query, query_start                                                              +| 2026-02-14 17:58:35.473337+00
     |           |        | FROM pg_stat_activity                                                                                       +|
     |           |        | WHERE state = 'active';                                                                                      |
 308 | discourse | active | DELETE                                                                                                      +| 2026-02-14 17:26:08.12598+00
     |           |        |   FROM calendar_events ce                                                                                   +|
     |           |        | WHERE                                                                                                       +|
     |           |        |   ce.id IN (SELECT DISTINCT(ce3.id) FROM calendar_events ce2                                                +|
     |           |        |             LEFT JOIN calendar_events ce3 ON ce3.user_id = ce2.user_id AND ce3.description = ce2.description+|
     |           |        |             WHERE ce2.start_date >= (ce3.start_date - INTERVAL '1 days')                                    +|
     |           |        |               AND ce2.start_date <= (ce3.start_date + INTERVAL '1 days')                                    +|
     |           |        |               AND ce2.timezone IS NOT NULL                                                                  +|
     |           |        |               AND ce3.timezone IS NULL                                                                      +|
     |           |        |               AND ce3.id != ce2.id                                                                          +|
     |           |        |               AND ce2.post_id IS NULL                                                                       +|
     |           |        |               AND ce3.post_id IS NULL                                                                       +|
     |           |        |   )                                                                                                         +|
     |           |        |                                                                                                              |
(2 rows)

이것은 다음 코드에서 나온 것입니다:

https://github.com/discourse/discourse/blob/main/plugins/discourse-calendar/db/migrate/20231124021939_delete_similar_holidays.rb

이것은 최신 마이그레이션이 더 뒤의 버전임을 보여줍니다. 하지만 아마도 해당 마이그레이션이 방금 추가된 플러그인에서 온 것이기 때문일 것입니다.

discourse=# SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;
    version
----------------
 20250507013646
(1 row)

캘린더 이벤트가 정말 많습니다!!! 69,724,384개!

discourse=# select count(*) from calendar_events;
  count
----------
 69724384
(1 row)

그러니까 이것이 문제인 것 같습니다.

. . . 하지만 소스 사이트에는 discourse_calendar가 설치되어 있지도 않다는 말입니다!?

하지만 소스 테이블의 calendar_events에는 그 수만큼의 이벤트가 있습니다. . . .

하지만 캘린더 이벤트가 있는 post_custom_fields는 단 4개뿐입니다. ?

즉, 토픽이나 포스트와 연결되지 않은 이벤트가 많이 있습니다. 크리스마스, 새해, 박싱데이 같은 표준 공휴일이죠.

해결 방법

delete from  calendar_events where topic_id=0 and post_id is null and post_number is null;

그 결과 99개의 이벤트가 남았습니다.

아마도 SiteSetting.delete_expired_event_posts_after가 0으로 설정되어 있었고, 플러그인을 실제로 사용하지 않았음에도 불구하고 이러한 모든 이벤트가 생성된 것이 아닐까 싶습니다?

그리고 맞습니다! SiteSetting.delete_expired_event_posts_after-1로 설정되어 있습니다.

아, 하지만 -1은 기본값입니다. 그래서 이것이 문제인 걸까요?

아니요. 그것은 이벤트 포스트에 대한 것입니다. 7천만 개의 이벤트가 어떻게 생겼는지는 정확히 모르겠습니다.

https://github.com/discourse/discourse/blob/main/plugins/discourse-calendar/config/settings.yml#L11-L13