{"id":"IL-0242","org":"Matrix.org Foundation","date":"2025-09-02","docs":[],"title":{"en":"Both PostgreSQL servers behind matrix.org were lost during a disk expansion, and the homeserver was down for 24 hours","ko":"primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다"},"domain":["database"],"impact":{"scope":"matrix.org homeserver unavailable from 2025-09-02 17:45 UTC to 2025-09-03 18:00 UTC; no data was lost","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Most of the difficulty came from the size of the database: restoring 51TB and replaying WALs took far longer than hoped, and incremental backups restore much faster than WAL replay. Running a destructive command on the wrong server was the turning point, and Matrix.org looks for better tooling and processes rather than blaming the individual; working as a trio did not catch the mistake, and server names that suggest a role caused confusion because a primary and a secondary swap roles. The recovery lost no data, but the recovery time needs to be shorter, and the wal-g versions on the disaster recovery server and the production servers had drifted apart.","ko":"이번 장애가 이렇게 힘들었던 데에는 데이터베이스가 너무 크다는 점이 크게 작용했다. 엉뚱한 서버에서 파괴적인 명령을 실행한 것이 결정적인 순간이었다. 하지만 팀은 개인을 탓하지 않고, 사람이 쓰는 도구와 절차를 고쳐 같은 일이 다시 일어날 가능성을 줄여야 한다고 보았다. 세 사람이 함께 작업했지만 급박한 상황에서는 실수를 잡지 못했다. db-01 은 primary, db-02 는 secondary 처럼 읽히지만 실제 역할은 서로 바뀔 수 있으므로, 뜻이 담긴 서버 이름은 혼란을 부른다. primary 와 secondary 를 모두 잃고도 데이터를 잃지 않은 것은 정기적으로 시험한 특정 시점 복구 절차 덕분이었다. 다만 복구 목표 시간은 줄여야 한다. 재해 복구 시험에서 찾은 wal-g 수정이 운영 서버에 빠져 있었으므로, 재해 복구 서버와 운영 서버를 같게 맞춰야 한다."},"related":[],"sources":[{"url":"https://matrix.org/blog/2025/10/post-mortem/","kind":"blog","archived":"https://web.archive.org/web/20260817074836/https://matrix.org/blog/2025/10/post-mortem/"}],"summary":{"en":"The matrix.org homeserver is a main Synapse instance with hundreds of workers on one logical PostgreSQL cluster of two bare-metal servers at Mythic Beasts, each with its own mdraid RAID 10 array. The secondary replicates from the primary through streaming replication, and the primary archives WALs continuously to an S3 bucket in AWS, alongside weekly full backups and daily incremental backups. At the time db-02 was the primary and db-01 the secondary. When the database reached about 51TB, 90% of disk capacity, Mythic Beasts added two NVMe drives to each server on 2 September 2025, and at 11:17 UTC an existing drive disappeared from db-02's array. The team failed over to db-01 at 13:27 UTC, restarted db-02 as a replica, and once it had caught up decided at 13:53 UTC to reboot it to repair the array. db-02 came back in recovery mode because another drive was missing; the RAID headers were gone on both missing drives, so a fresh array was created, and db-02 was back online at 16:11 UTC. WAL archiving from db-01 to S3 was unblocked at 16:50 UTC.\n\nAt 17:20 UTC PostgreSQL on the empty db-02 was upgraded to the latest patch version, and at 17:25 UTC the team began restoring it with wal-g. Two failed attempts left directories in the data path, and while removing them the data directory of the primary on db-01 was deleted by mistake, although the on-call group was working as a trio. PostgreSQL on db-01 was left running in case open file handles kept the data on disk. From 17:30 UTC db-02 was restored from the offsite backups in S3, Synapse and the damaged primary were shut down, and db-01's data partition was remounted read-only at 18:06 UTC. The team decided against undeletion: extundelete and ext4magic had been unmaintained for a decade and cannot work on an unmounted filesystem, and the files recovered with R-Linux could not be trusted. The WALs that db-01 had never archived to S3 were copied to db-02 for the replay.\n\nThe full weekly backup finished extracting at 07:21 UTC the next morning, but wal-g failed on the incremental backups because of a known bug. The team updated wal-g, patched it to accept a data directory that already held the full base backup so nothing had to be pulled from S3 again, and restored the incremental backups between 09:35 and 10:03 UTC. With a restore_command wrapper that fetched WALs from S3 and from the files copied from db-01, PostgreSQL replayed the WALs in standby mode from 11:00 to 16:27 UTC. db-02 was promoted to primary at 16:45 UTC and Synapse was started at 16:54 UTC; after several overloads as everyone reconnected at once, all workers were online and stable by 18:00 UTC. No data was lost. Later, db-01 failed to boot because it was rebooted while its array was reshaping after the new disks were added; once the array was fixed, db-01 was rebuilt as a replica from a base backup of db-02.","ko":"matrix.org homeserver 는 수백 개 worker 를 둔 Synapse 인스턴스와, 서버 두 대로 이루어진 논리 Postgres 클러스터 하나로 돌아간다. primary 는 streaming replication 으로 읽기 전용 secondary 에 복제되고, S3 에 주간 전체 백업과 일일 증분 백업을 남기며 WAL 을 계속 보관한다. 사고 당시 primary 서버 이름은 db-02, secondary 서버 이름은 db-01 이었다. 데이터베이스가 약 51 TB 로 디스크의 90% 에 이르자 팀은 디스크를 더하기로 했다. 2025년 9월 2일 11:03 UTC 에 호스팅 업체 Mythic Beasts 가 두 서버에 NVMe 드라이브를 2개씩 넣었다. 11:17 에 db-02 의 RAID 배열에서 기존 드라이브 하나가 사라져 팀은 13:27 에 db-01 로 장애 조치를 마쳤다. 새 primary 는 보관 스크립트 문제로 WAL 을 S3 로 보내지 못했다. 14:01 에 db-02 를 재시작하자 드라이브가 하나 더 빠져 서버가 복구 모드로 부팅됐고, 팀은 RAID 배열을 새로 만들어 16:11 에 db-02 를 다시 올렸다. 16:50 에는 막혀 있던 db-01 의 WAL 을 S3 로 보관하는 일도 다시 돌아가게 했다.\n\n17:25 에 db-02 에 백업을 복원하려 했지만, 명령을 데이터 디렉터리에서 실행해 tee 가 실패하며 복원이 중단됐다. 다시 실행하자 이번에는 데이터 디렉터리가 비어 있지 않다는 이유로 실패했다. 남은 것을 지우던 운영자가 db-01 에 있던 primary 의 데이터 디렉터리를 지웠다. 세 사람이 함께 작업하고 있었지만 이 실수를 막지 못했다. 팀은 17:30 에 wal-g 로 S3 백업을 db-02 에 복원하기 시작했고, Synapse 와 primary 를 내렸다. 이때부터 matrix.org 가 멈췄다. 지운 파일을 되살리는 도구 가운데 extundelete 와 ext4magic 은 10년 동안 관리되지 않았고, 마운트하지 않은 파일 시스템에서는 동작하지 않았다. R-Linux 로 살린 파일은 무결성을 믿을 수 없었다. 팀은 지운 파일을 살리는 대신 백업 복원에 기대기로 했다. S3 로 가지 못한 WAL 은 db-01 에서 db-02 로 복사해 두었다.\n\n이튿날 07:21 UTC 에 주간 전체 백업 추출이 끝났지만, 운영 서버의 wal-g 가 이미 수정된 결함이 있는 옛 버전이라 증분 백업 복원이 실패했다. 팀은 재해 복구 시험에서 이 결함을 만났지만, 시험용 ansible playbook 에만 수정을 넣고 운영 서버에는 빠뜨렸다. 팀은 wal-g 를 올리고, 전체 백업이 이미 들어 있는 데이터 디렉터리를 받아들이도록 wal-g 를 고쳐 빌드했다. 09:23 부터 증분 백업 네 개를 복원해 10:03 에 끝냈다. S3 와 파일 시스템 양쪽에서 WAL 을 가져오는 restore_command 를 설정해 standby 모드로 Postgres 를 띄웠고, 약 18시간 분량의 WAL 을 재생하는 데 5.5시간이 걸려 16:27 에 끝났다. 16:45 에 db-02 를 primary 로 승격했고, 16:54 에 Synapse 를 시작했다. 모두가 한꺼번에 다시 연결하면서 여러 번 과부하가 났지만, 18:00 에 모든 worker 가 안정됐다. 데이터는 잃지 않았다. 그 뒤 백업을 primary 에서 받도록 바꾸고, 09:21 UTC 에 db-01 에 새 디스크를 넣은 뒤 db-02 의 basebackup 을 복사해 복제를 다시 시작했다."},"trigger":{"en":"During maintenance to add drives, an existing drive disappeared from the primary database server's RAID 10 array at 11:17 UTC, which led to a failover and a rebuild of that server. The outage itself began when the data directory of the new primary on db-01 was deleted while clearing a failed restore on db-02.","ko":"디스크 용량을 늘리는 정기 작업 중 primary 데이터베이스 서버 db-02 의 RAID 배열에서 드라이브가 빠져 secondary 인 db-01 로 장애 조치를 했다. 그 뒤 db-02 를 복원하던 운영자가 실패한 복원의 흔적을 지우려다 db-01 에 있던 새 primary 의 데이터 디렉터리를 지웠다."},"severity":"unknown","symptoms":["service-unavailable","data-loss"],"timeline":{"detected_after":null,"resolved_after":"24h 15m"},"date_kind":"incident","detection":{"en":"Monitoring fired when the drive disappeared from db-02's RAID array at 11:17 UTC, and Mythic Beasts confirmed the issue. The team realised it had deleted db-01's data directory, and the backend team was then paged, an incident was opened and an emergency was declared.","ko":"11:17 UTC 에 db-02 의 RAID 배열에서 드라이브가 빠지자 모니터링 알림이 울렸고 Mythic Beasts 가 문제를 확인했다. 운영자가 db-01 의 데이터 디렉터리를 지운 것을 스스로 알아챘다. 그 뒤 백엔드 팀을 호출하고, 장애 대응을 시작하며 비상 상황을 선언했다."},"components":["database","database-backup","database-replication"],"root_cause":"human-operation","remediation":[{"text":{"en":"Created a fresh RAID 10 array on db-02","ko":"16:11 UTC 에 새로 만든 RAID 10 배열로 db-02 서버를 다시 올렸다."}},{"text":{"en":"Unblocked WAL archiving from the new primary on db-01 to S3","ko":"16:50 UTC 에 db-01 에 있던 primary 에서 막혀 있던 WAL 의 S3 보관을 다시 돌아가게 했다. 그래서 db-01 이 쌓인 WAL 을 지울 수 있게 됐다."}},{"text":{"en":"Started restoring db-02 from the S3 backups with wal-g","ko":"17:30 UTC 에 S3 에서 백업을 가져오는 wal-g 로 db-02 에 데이터베이스 복원을 시작했다."}},{"text":{"en":"Shut down Synapse and the damaged primary database","ko":"알 수 없는 데이터베이스 상태를 피하려고 Synapse 와 db-01 의 primary 데이터베이스를 모두 내렸다."}},{"text":{"en":"Remounted the data partition of db-01 read-only","ko":"18:06 UTC 에 db-01 의 데이터 파티션을 읽기 전용으로 다시 마운트해, 나중에 살릴 경우에 대비해 데이터베이스가 더 망가지지 않게 했다."}},{"text":{"en":"Copied the WALs missing from S3 from db-01 to db-02","ko":"S3 로 보관되지 못한 WAL 을 db-01 에서 db-02 로 복사해, S3 백업 복원이 끝나면 바로 재생할 수 있게 준비했다."}},{"text":{"en":"Updated wal-g and patched it to continue from the partial restore","ko":"wal-g 를 같은 메이저 버전의 최신 릴리스로 올린 뒤, 전체 백업이 이미 들어 있는 비어 있지 않은 데이터 디렉터리를 받아들이도록 wal-g 를 고쳐 빌드했다. 09:23 UTC 에 이 wal-g 를 db-02 에 써서 S3 에서 다시 받지 않고 증분 백업을 복원했다."}},{"text":{"en":"Configured a restore_command wrapper that fetched WALs from S3 and the local files, and replayed them in standby mode","ko":"S3 와 db-01 에서 옮겨 온 파일 시스템 양쪽에서 WAL 을 가져오는 래퍼 스크립트로 restore_command 를 설정하고 Postgres 를 standby 모드로 띄웠다. 11:00 UTC 부터 WAL 을 재생했다."}},{"text":{"en":"Promoted db-02 to primary and restarted Synapse","ko":"16:45 UTC 에 db-02 를 primary 데이터베이스로 승격하고, Synapse 시험 worker 를 띄워 새 WAL 이 S3 에 올라오는 것을 확인한 뒤 16:54 에 Synapse 를 시작했다. 18:00 에 모든 worker 가 안정됐다."}},{"text":{"en":"Ran backups against the primary while no replica existed","ko":"WAL 재생이 느렸기 때문에, 없어진 복제본 대신 primary 데이터베이스에서 백업을 받도록 다시 설정했다. 03:26 UTC 에 새 증분 백업이 끝났다."}},{"text":{"en":"Rebuilt db-01 as a replica from a base backup of db-02","ko":"db-01 에 NVMe 디스크 두 개를 RAID 배열과 LVM 볼륨 그룹에 넣었다. 배열 재구성 중 재부팅해 서버가 올라오지 않자 배열을 부팅할 수 있는 상태로 고쳤고, db-02 의 basebackup 을 복사해 db-01 을 다시 복제 상태로 두었다."}}],"action_items":[{"text":{"en":"Run pg_repack to reclaim free space","ko":"저장 공간이 늘어난 지금 pg_repack 을 돌려 빈 공간을 되찾는다."}},{"text":{"en":"Take incremental backups more often, since they restore faster than WAL replay","ko":"증분 백업은 WAL 재생보다 훨씬 빨리 복원되므로 증분 백업을 더 자주 받도록 이미 바꿨다."}},{"text":{"en":"Reduce Synapse's storage requirements","ko":"Synapse 가 데이터를 저장하는 방식을 고쳐 앞으로 필요한 저장 공간을 크게 줄인다."}},{"text":{"en":"Consider changing the terminal background colour by the database's role, for example by watching for the standby.signal file","ko":"데이터베이스가 클러스터에서 맡은 역할에 따라 터미널 배경색을 바꾸는 방법을 검토한다. 데이터 디렉터리에 standby.signal 파일이 있는지 보고 primary 인지 secondary 인지 판단해 배경색을 바꾸는 안이 나왔다."}},{"text":{"en":"Consider wrapper scripts around sensitive commands, such as an alias for rm, and automating operations such as starting a base backup from primary to secondary","ko":"rm 의 alias 처럼 위험한 명령을 감싸는 래퍼 스크립트를 두거나, primary 에서 secondary 로 base backup 을 시작하는 일 같은 작업을 자동화하는 방법을 논의했다."}},{"text":{"en":"Shorten the recovery time, with infrastructure from service providers","ko":"복구 목표 시간을 줄인다. 더 쉽고 빠르게 복구할 수 있는 무료 인프라를 얻으려고 서비스 제공자들과 이야기하고 있다."}},{"text":{"en":"Upgrade wal-g on all servers and repeat disaster recovery testing with it","ko":"재해 복구 서버뿐 아니라 모든 서버의 wal-g 를 올렸고, 올린 wal-g 로 재해 복구 시험을 한 차례 했다. 재해 복구 서버와 운영 서버를 계속 같게 유지하는 방법은 아직 찾지 않았다."}},{"text":{"en":"Explore ZFS local snapshots at the next hardware refresh","ko":"다음 하드웨어 교체 때 ZFS 를 검토해, 엉뚱한 데이터베이스를 지우는 사고에서도 로컬 스냅숏으로 훨씬 빨리 복구할 수 있게 한다."}},{"text":{"en":"Make status.matrix.org the source of truth during incidents and keep social media updated","ko":"장애 대응 절차에 status.matrix.org 를 장애 중 공식 정보원으로 갱신하는 단계를 넣고, advocacy 팀과 함께 소셜 미디어도 계속 갱신한다."}}],"contributing":["overlapping-failures","manual-action-worsened","known-bug-unpatched","no-safeguard-on-destructive-change"],"recurrence_of":null,"symptoms_text":{"en":"On 2 September 2025 the matrix.org homeserver went down at 17:45 UTC and stayed down until 18:00 UTC the next day. Both machines of its PostgreSQL cluster were out of action: the original primary had lost its RAID array during a disk expansion, and the data directory of the new primary had been deleted by mistake.\n\nThe trouble began at 11:17 UTC, when one existing drive disappeared from the RAID 10 array of db-02, the primary database server, shortly after the hosting provider Mythic Beasts added two NVMe drives to each server. Monitoring fired and the array kept working in degraded mode. After a planned failover to db-01 at 13:27 UTC, a reboot of db-02 at 14:01 UTC left it in recovery mode, with no network, no ssh and no PostgreSQL, because another drive was now missing and the array could not be assembled. Meanwhile the new primary on db-01 did not archive WALs to S3 because of an issue in the archiving script, and WALs kept piling up on db-01.\n\nWhile restoring a replica on the rebuilt db-02, the first wal-g backup-fetch run aborted because it was started from inside the PostgreSQL data directory, which made its tee command fail, and the second run failed because the aborted restore had left the data directory non-empty. While clearing those leftovers, the data directory of the primary on db-01 was deleted instead. The partly wiped primary threw errors without crashing Synapse, and the team shut down Synapse and the database.\n\nThe restore from S3 then hit more problems. After the full weekly backup had been extracted at 07:21 UTC the next morning, wal-g failed on the first daily incremental backup because of a wal-g bug whose fix had been applied to the disaster recovery test playbook but not to the production servers. PostgreSQL on db-02 then refused to start in standby mode because it could reach no primary and had no restore_command set. Replaying about 18 hours of WALs took 5.5 hours, where the team had hoped for about 10 minutes per hour of WALs.","ko":"2025년 9월 2일 디스크 용량을 늘리는 작업 중 11:17 UTC 에 primary 데이터베이스 서버 db-02 의 RAID 배열에서 기존 드라이브 하나가 사라졌고 모니터링 알림이 울렸다. RAID 10 이라 데이터는 잃지 않았고 배열은 degraded 모드로 계속 동작했지만, 드라이브가 하나 더 고장 나면 버티지 못할 수 있었다.\n\n13:27 에 secondary 인 db-01 로 장애 조치를 마쳤고 Synapse 는 새 primary 에 문제없이 쓰기 시작했다. 하지만 새 primary 에서는 WAL 을 S3 로 보관하는 스크립트가 오류를 내 WAL 이 S3 로 가지 않았고, secondary 도 꺼져 있어 db-01 에 WAL 이 계속 쌓였다.\n\n14:01 에 db-02 를 재시작하자 드라이브가 하나 더 빠져 RAID 배열을 조립하지 못했고, 서버는 복구 모드로 부팅됐다. 복구 모드에서는 네트워크도 ssh 도 없었고 postgres 도 돌지 않았다. 15:44 에 팀은 빠진 두 드라이브 모두에서 RAID 헤더가 사라져 배열을 되살릴 수 없다고 판단했다.\n\n17:25 에 db-02 에 백업을 복원하려고 wal-g backup-fetch 명령을 Postgres 데이터 디렉터리에서 실행하자 tee 명령이 실패하며 복원이 중단됐다. 홈 경로로 옮겨 다시 실행하자 앞선 복원이 남긴 디렉터리 때문에 데이터 디렉터리가 비어 있지 않다는 이유로 실패했다. 운영자는 남은 것을 지우려다 db-01 에 있던 primary 의 데이터 디렉터리를 지웠다.\n\n일부가 지워진 db-01 의 primary 는 오류를 냈지만 Synapse 를 죽일 만큼 망가지지는 않았다. 팀은 알 수 없는 데이터베이스 상태를 피하려고 Synapse 와 primary 를 내렸고, 이때부터 matrix.org homeserver 가 멈췄다.\n\n이튿날 07:21 UTC 에 주간 전체 백업 추출이 끝났지만, wal-g 는 일일 증분 백업을 복원하기 시작하자마자 오류를 냈다. 이미 수정이 나온 wal-g 결함이었다. 재해 복구 시험용 ansible playbook 에는 수정을 넣었지만 실제 데이터베이스 서버에는 빠져 있었다. 10:45 에는 닿을 수 있는 primary 도 없고 restore_command 도 설정되지 않아 Postgres 가 standby 모드로 시작하기를 거부했다. WAL 재생도 예상보다 느려 약 18시간 분량을 재생하는 데 5.5시간이 걸렸다."},"updated_at":"2026-11-14T12:33:49.013440Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0242","org_slug":"matrix-org-foundation"}