Incident Library

IL-0242 Matrix.org Foundation · 2025-09-02database

primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다

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 로 보관하는 일도 다시 돌아가게 했다.

17: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 로 복사해 두었다.

이튿날 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 을 복사해 복제를 다시 시작했다.

심각도
–
영향 범위
matrix.org homeserver unavailable from 2025-09-02 17:45 UTC to 2025-09-03 18:00 UTC; no data was lost
영향 사용자
–
지역
–
감지까지
–
복구까지
24h 15m

증상

2025년 9월 2일 디스크 용량을 늘리는 작업 중 11:17 UTC 에 primary 데이터베이스 서버 db-02 의 RAID 배열에서 기존 드라이브 하나가 사라졌고 모니터링 알림이 울렸다. RAID 10 이라 데이터는 잃지 않았고 배열은 degraded 모드로 계속 동작했지만, 드라이브가 하나 더 고장 나면 버티지 못할 수 있었다.

13:27 에 secondary 인 db-01 로 장애 조치를 마쳤고 Synapse 는 새 primary 에 문제없이 쓰기 시작했다. 하지만 새 primary 에서는 WAL 을 S3 로 보관하는 스크립트가 오류를 내 WAL 이 S3 로 가지 않았고, secondary 도 꺼져 있어 db-01 에 WAL 이 계속 쌓였다.

14:01 에 db-02 를 재시작하자 드라이브가 하나 더 빠져 RAID 배열을 조립하지 못했고, 서버는 복구 모드로 부팅됐다. 복구 모드에서는 네트워크도 ssh 도 없었고 postgres 도 돌지 않았다. 15:44 에 팀은 빠진 두 드라이브 모두에서 RAID 헤더가 사라져 배열을 되살릴 수 없다고 판단했다.

17:25 에 db-02 에 백업을 복원하려고 wal-g backup-fetch 명령을 Postgres 데이터 디렉터리에서 실행하자 tee 명령이 실패하며 복원이 중단됐다. 홈 경로로 옮겨 다시 실행하자 앞선 복원이 남긴 디렉터리 때문에 데이터 디렉터리가 비어 있지 않다는 이유로 실패했다. 운영자는 남은 것을 지우려다 db-01 에 있던 primary 의 데이터 디렉터리를 지웠다.

일부가 지워진 db-01 의 primary 는 오류를 냈지만 Synapse 를 죽일 만큼 망가지지는 않았다. 팀은 알 수 없는 데이터베이스 상태를 피하려고 Synapse 와 primary 를 내렸고, 이때부터 matrix.org homeserver 가 멈췄다.

이튿날 07:21 UTC 에 주간 전체 백업 추출이 끝났지만, wal-g 는 일일 증분 백업을 복원하기 시작하자마자 오류를 냈다. 이미 수정이 나온 wal-g 결함이었다. 재해 복구 시험용 ansible playbook 에는 수정을 넣었지만 실제 데이터베이스 서버에는 빠져 있었다. 10:45 에는 닿을 수 있는 primary 도 없고 restore_command 도 설정되지 않아 Postgres 가 standby 모드로 시작하기를 거부했다. WAL 재생도 예상보다 느려 약 18시간 분량을 재생하는 데 5.5시간이 걸렸다.

증상 서비스 중단데이터 유실

원인

촉발
디스크 용량을 늘리는 정기 작업 중 primary 데이터베이스 서버 db-02 의 RAID 배열에서 드라이브가 빠져 secondary 인 db-01 로 장애 조치를 했다. 그 뒤 db-02 를 복원하던 운영자가 실패한 복원의 흔적을 지우려다 db-01 에 있던 새 primary 의 데이터 디렉터리를 지웠다.

감지

11:17 UTC 에 db-02 의 RAID 배열에서 드라이브가 빠지자 모니터링 알림이 울렸고 Mythic Beasts 가 문제를 확인했다. 운영자가 db-01 의 데이터 디렉터리를 지운 것을 스스로 알아챘다. 그 뒤 백엔드 팀을 호출하고, 장애 대응을 시작하며 비상 상황을 선언했다.

조치

후속 조치

교훈

이번 장애가 이렇게 힘들었던 데에는 데이터베이스가 너무 크다는 점이 크게 작용했다. 엉뚱한 서버에서 파괴적인 명령을 실행한 것이 결정적인 순간이었다. 하지만 팀은 개인을 탓하지 않고, 사람이 쓰는 도구와 절차를 고쳐 같은 일이 다시 일어날 가능성을 줄여야 한다고 보았다. 세 사람이 함께 작업했지만 급박한 상황에서는 실수를 잡지 못했다. db-01 은 primary, db-02 는 secondary 처럼 읽히지만 실제 역할은 서로 바뀔 수 있으므로, 뜻이 담긴 서버 이름은 혼란을 부른다. primary 와 secondary 를 모두 잃고도 데이터를 잃지 않은 것은 정기적으로 시험한 특정 시점 복구 절차 덕분이었다. 다만 복구 목표 시간은 줄여야 한다. 재해 복구 시험에서 찾은 wal-g 수정이 운영 서버에 빠져 있었으므로, 재해 복구 서버와 운영 서버를 같게 맞춰야 한다.

출처

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스, 데이터베이스 백업과 스냅숏, 데이터베이스 복제. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.