사례8건
8건 중 1~5번째
- 증상
실버 위크 연휴가 끝난 첫날, 엔지니어가 로컬 데이터베이스를 비우고 다시 시작하려고 prisma migrate reset 을 실행하고 시드 데이터를 넣었다. 그런데 시드 데이터가 들어가지 않은 것처럼 보였다.
- 촉발
엔지니어가 로컬 데이터베이스를 대상으로 한다고 믿고 prisma migrate reset 을 실행했는데, packages/database/.env 에 연휴 전에 넣어 둔 운영 DATABASE_URL 이 남아 있었다.
- 원인
- 구성 요소
- 기여 요인
- 감지
엔지니어가 prisma migrate reset 뒤에 넣은 시드 데이터가 로컬에 보이지 않는 것을 보고, 운영 데이터베이스를 초기화한 것을 알아챘다.
- 조치
- Neon 의 Backup & Restore 로 운영 데이터베이스를 되살렸다.
- 후속 조치
- 데이터베이스를 지우는 명령 앞에 DATABASE_URL 이 localhost 나 127.0.0.1 을 가리키는지 확인하고 아니면 멈추는 스크립트를 둔다.
- 로컬 .env 에 운영 데이터베이스의 접속 문자열을 쓰지 않는다.
- 운영 데이터베이스를 대상으로 하는 작업은 CI 나 시크릿 매니저를 거쳐서 하도록 바꾼다.
- 개발자 컴퓨터의 env 파일에 *.neon.tech 같은 운영 호스트가 들어 있지 않은지 확인한다.
- 쓰는 요금제의 Backup & Restore 기간이 부족하면 이력 보관 기간을 늘리거나 따로 백업을 마련한다.
- 연휴처럼 오래 손을 놓기 전에 로컬 env 에서 운영 URL 을 지운다.
- 작업이 중간에 멈추면 어디까지 했는지를 이슈나 메모에 남긴다.
- AI 에이전트 규칙에, 사용자가 그 메시지에서 분명히 요청하지 않으면 prisma migrate reset, db push --force-reset 같은 데이터베이스를 지우는 명령을 실행하지 않는다는 규칙을 넣는다.
- 교훈
글쓴이는 기억보다 기록을 믿어야 한다고 정리했다. 오래 손을 놓기 전에는 어중간한 상태를 남기지 않고, 남겨야 하면 메모로 적어 둔다. 데이터베이스를 지우는 명령은 접속 대상이 localhost 가 아니면 실행되지 않아야 한다. 운영 URL 은 개발자 컴퓨터에 두지 않고, AI 에이전트에도 금지 규칙을 둔다. Backup & Restore 는 기능이 있는지와 별도로, 쓰는 요금제의 기간으로 충분한지를 확인해야 한다. 글쓴이는 이번에 피해가 작았던 것은 서비스가 마침 초기였기 때문이라고 했다.
- 증상
2026-04-28 10:34 BST에 새 데이터베이스로 전환한 뒤 11:30 무렵 운영에서 데이터 이상이 보였다. 이전 직전에 쓴 짧은 기간의 데이터가 새 데이터베이스로 복제되지 않았고, 서비스는 전환 때부터 약 3시간 저하됐다.
- 촉발
2026-04-20 16:20 BST 무렵 관련 없는 설정 수정 작업 중에 자격 증명을 교체했다. 그 자격 증명으로 데이터베이스 연결을 잡고 있던 임시 일회성 복제 프로세스가 죽었고, 그 순간 처리 중이던 데이터 묶음이 버려진 뒤 다시 재생되지 않았다.
- 원인
- 구성 요소
- 기여 요인
- 감지
2026-04-28 11:30 BST 무렵 운영에서 데이터 이상을 발견하고 조사를 시작했다. 8일 전의 손실은 그때까지 아무 신호도 없었다.
- 조치
- 근본 원인을 찾는 일보다 서비스의 정상 동작을 되살리는 일을 먼저 했고, 전환 약 3시간 뒤인 13:30 BST 무렵 정상 동작이 돌아왔다.
- 버려진 묶음을 다시 재생할 원본으로 쓰려고 이전 사업자에게 지운 원본 데이터베이스의 복원을 요청했다.
- 2026-04-29부터 2026-04-30까지 원본 데이터베이스를 복원하고 영향을 받은 사용자의 기록을 다시 재생했다. 이 동안 서비스는 더 멈추지 않았다.
- 후속 조치
- 위험도가 비슷한 기술 변경은 라이브 전환 대신 계획된 유지보수 시간 안에 한다. 트래픽을 다시 받기 전에 무결성을 확인할 수 있도록 짧은 계획 중단을 받아들인다.
- 이런 변경에는 전환 전 점검표를 두고, 전환 직전에 데이터 복제나 동기화 프로세스가 살아 있고 따라잡았는지 확인한다.
- 오래 도는 프로세스가 쓰는 서비스 계정의 자격 증명을 교체할 때는 그 뒤에 영향을 받을 곳을 명시적으로 검토한다.
- 전환 뒤 대상 쪽의 무결성을 따로 확인하기 전에는 원본 시스템을 내리거나 지우지 않는다.
- 계획된 유지보수와 이전을 고객에게 알릴 때 Discord와 함께 웹 콘솔에서 보이는 제품 안 News 피드에도 올리고, 영향을 분명히 적고, 이번보다 훨씬 일찍 알린다.
- 교훈
이전 작업 자체는 문제가 없었고, 실패는 8일 전 관련 없는 변경이 조용히 남긴 부작용이었다. 복제 프로세스가 다시 살아나 정상으로 돌았기 때문에 데이터를 잃었다는 신호가 없었고, 전환 전에 데이터 무결성을 확인했다면 트래픽을 다시 받기 전에 잡았을 것이다. 새 데이터베이스의 무결성을 확인하기 전에 원본 데이터베이스를 지운 탓에 복구가 크게 복잡해졌으므로 앞으로는 전환 뒤 확인이 끝날 때까지 원본을 남긴다. 중단을 줄이려고 고른 라이브 이전에는 트래픽이 돌아오기 전에 무결성을 확인할 틈이 없었다. 그 결과 약 3시간의 서비스 저하와 이틀의 복구가 이어졌고, 이는 계획된 유지보수로 서비스를 잠깐 멈췄을 때보다 훨씬 큰 영향이었다. Discord 공지는 영향이 분명하지 않았고 고객이 대비할 시간을 충분히 주지 못했다.
- 증상
가용 영역 장애 대비 훈련을 마친 뒤 정리 단계에서 런북대로 Kafka 브로커를 없애자, 운영 환경의 여러 토픽 파티션이 리더 없이 남았다. 망가진 파티션만 배정된 팀과 환경은 데이터 수집이 실패했다.
- 촉발
12월 5일 운영 환경에서 가용 영역 장애 훈련을 마친 뒤, 워크로드 균형을 되돌리는 정리 단계를 런북대로 진행하다 Kafka 브로커를 일부러 없앴다. 이때 동기화가 끝나 있던 기존 브로커까지 함께 없어졌다.
- 원인
- 구성 요소
- 기여 요인
- 감지
대응자들은 알림이 쏟아지는 가운데 수집 서비스 Shepherd 의 크래시 루프를 한 시간 넘게 지나서야 알아챘다. 토요일 아침에는 대응자들이 Kafka 밸런싱 작업이 나아가지 않고 디스크 사용량이 줄지 않는 것을 봤다. 메타데이터 토픽을 고치자 SLO 처리 트리거가 약 18시간 늦은 데이터를 알렸다.
- 조치
- 망가진 파티션을 프로듀서 쪽에서 읽기 전용으로 돌려 수집 서비스의 크래시 루프를 멈췄다.
- 파티션 재배치를 돌려 고객들의 수집을 정상으로 되돌렸다.
- Kafka autobalancer 를 끄고, 균형을 일부 되찾는 수동 재배치 계획을 세워 밤새 돌렸다.
- 자정 무렵 PT 에 브로커 디스크가 너무 빨리 차자 Kafka 의 로컬 보존 기간을 줄이고 긴 보존은 S3 tiered storage 에 맡기려 했다.
- _confluent-tier-state 와 __consumer_offsets 토픽의 깨진 파티션을 고쳤다. 그러자 막혀 있던 SLO 처리 서비스가 늦은 데이터를 따라잡기 시작했다.
- 토요일 아침 7시 PT 에 디스크 여유 공간이 5.07% 일 때 수집을 껐다. 디스크 사용량은 멈췄지만 줄지 않았다.
- tiered storage 를 끄자 디스크 공간이 바로 돌아왔다. 2021년 예전 장애 때 통했던 방법이었다.
- 수집을 다시 열었고, 이때 고객 영향이 끝났다.
- 그날 늦게 완전히 망가진 Retriever 토픽 파티션을 손으로 고쳐 잘라 내고 읽기 전용으로 두었다. Retriever 가 offset 초기화를 감당하지 못해 오래된 데이터를 한동안 조회할 수 없었지만 데이터를 잃지는 않았다.
- Activity Log 가 Kafka 로 이벤트를 보내지 못하게 되자 Honeycomb MySQL 읽기 전용 복제본의 binlog 보존 기간을 늘렸다.
- offset 이 망가졌어도 Retriever 노드가 죽지 않고 대기 상태로 남아 오래된 데이터를 조회할 수 있게, 작은 수정 여러 개를 넣었다.
- 금요일 정오 무렵 Storage 팀이 Retriever 의 offset 초기화 장치가 동작하게 만들었다.
- 새 Kafka 클러스터로 옮기는 마이그레이션을 화요일에 두 시간 만에 큰 문제 없이 끝냈고, 신호 지연은 많아야 한 시간이었다.
- 새 복제본으로 binlog 보존 기간을 늘리려던 방법이 통하지 않자, 12월 9일 화요일 시점의 인스턴스를 띄워 그때부터 복제해 Activity Log 손실을 줄였다.
- 옛 Kafka 클러스터를 정리하고 EU 리전의 Activity Log 일부를 따라잡았다.
- 후속 조치
- 조직이 커지는 만큼 여러 곳에 흩어진 정보를 더 잘 모은다.
- 교훈
Honeycomb 은 "다음에는 Kafka 클러스터를 없애지 말자" 는 설명에서 멈추지 않고 더 넓은 영향을 봐야 한다고 밝혔다. 사전 운영 클러스터는 노드가 적어 구조상 이 문제를 재현하기 어려웠고, 운영과 더 닮은 클러스터에서 시험했다면 잡을 가능성이 훨씬 높았다. 이번 장애에 얽힌 요소 상당수는 이미 알려져 있었고 우회 계획, RFC, 안전하게 훈련하는 절차를 적은 문서도 있었지만, 쓴 사람 말고는 그런 문서가 있는지 몰랐거나 검색으로 찾을 수 없었다. 마감이 멀고 범위가 불확실한 내부 작업은 급한 작업에 밀려 거듭 미뤄지기 쉽다. 핵심 개입 상당수는 이제 그 구성 요소를 맡지 않는 사람들에게서 나왔고, 이는 팀 구성과 기술 구성 사이에 틈이 벌어졌다는 뜻이다. 에스컬레이션을 더 빨리 했어야 했다는 의견과 대응을 지속 가능하게 해야 한다는 의견이 함께 나왔다. 선임 엔지니어들이 들어오자마자 저절로 지휘를 잡는 문제와, 베타 기능의 손실을 너무 자세히 알린 대외 소통도 과제로 남았다.
- 증상
2025년 8월 어느 날 10:41 에 시스템 개발 본부가 Slack 에 BigQuery 에서 장애가 났다는 첫 보고를 올렸다. 운영 데이터 웨어하우스의 데이터셋이 모두 지워져 있었다.
- 촉발
수정 중이던 테스트 코드가 테스트 환경의 데이터를 초기화하려고 데이터셋을 지우고 같은 이름으로 다시 만드는 처리를 했는데, 설정이 잘못되어 이 처리가 운영 환경의 모든 데이터셋에 실행됐다.
- 원인
- 구성 요소
- 기여 요인
- 감지
10:41 에 시스템 개발 본부가 Slack 에 BigQuery 에서 장애가 났다는 첫 보고를 올렸다. 11:47 에 시스템 개발 본부가 조사해 AI 기술 개발실의 서비스 계정이 운영 데이터셋을 지운 것을 찾았다.
- 조치
- Cloud Shell 에서 bq cp 명령에 장애 전 시점을 지정해 타임 트래블로 테이블을 하나씩 복원했고, 데이터 대부분을 잃지 않고 되살렸다.
- bq cp 로 되살릴 수 없는 뷰와 테이블 함수는 BigQuery 의 저장된 쿼리에 남아 있던 생성 쿼리로 다시 만들었다.
- 후속 조치
- 테스트 코드가 운영 환경에 접근하지 못하게 확실히 막는다.
- 서비스 계정에 주어져 있던 과한 권한을 정리한다.
- bq cp 로 복원하려면 테이블 이름을 알아야 하므로, 데이터셋과 테이블 정보를 메타데이터로 관리한다.
- bq cp 로는 뷰와 테이블 함수를 복원할 수 없으므로, 그 생성 쿼리를 GitHub 같은 곳에서 관리한다.
- BigQuery 의 백업 방식을 검토한다.
- 교훈
bq cp 로 복원하려면 테이블 이름을 알고 있어야 하고, 이 방법으로는 뷰와 테이블 함수를 되살릴 수 없다. 글쓴이의 팀은 BigQuery 의 타임 트래블 기능과 bq cp 명령 덕분에 데이터 대부분을 되살릴 수 있었다고 정리했다.
- 증상
2025년 9월 2일 디스크 용량을 늘리는 작업 중 11:17 UTC 에 primary 데이터베이스 서버 db-02 의 RAID 배열에서 기존 드라이브 하나가 사라졌고 모니터링 알림이 울렸다. RAID 10 이라 데이터는 잃지 않았고 배열은 degraded 모드로 계속 동작했지만, 드라이브가 하나 더 고장 나면 버티지 못할 수 있었다.
- 촉발
디스크 용량을 늘리는 정기 작업 중 primary 데이터베이스 서버 db-02 의 RAID 배열에서 드라이브가 빠져 secondary 인 db-01 로 장애 조치를 했다. 그 뒤 db-02 를 복원하던 운영자가 실패한 복원의 흔적을 지우려다 db-01 에 있던 새 primary 의 데이터 디렉터리를 지웠다.
- 원인
- 구성 요소
- 기여 요인
- 감지
11:17 UTC 에 db-02 의 RAID 배열에서 드라이브가 빠지자 모니터링 알림이 울렸고 Mythic Beasts 가 문제를 확인했다. 운영자가 db-01 의 데이터 디렉터리를 지운 것을 스스로 알아챘다. 그 뒤 백엔드 팀을 호출하고, 장애 대응을 시작하며 비상 상황을 선언했다.
- 조치
- 16:11 UTC 에 새로 만든 RAID 10 배열로 db-02 서버를 다시 올렸다.
- 16:50 UTC 에 db-01 에 있던 primary 에서 막혀 있던 WAL 의 S3 보관을 다시 돌아가게 했다. 그래서 db-01 이 쌓인 WAL 을 지울 수 있게 됐다.
- 17:30 UTC 에 S3 에서 백업을 가져오는 wal-g 로 db-02 에 데이터베이스 복원을 시작했다.
- 알 수 없는 데이터베이스 상태를 피하려고 Synapse 와 db-01 의 primary 데이터베이스를 모두 내렸다.
- 18:06 UTC 에 db-01 의 데이터 파티션을 읽기 전용으로 다시 마운트해, 나중에 살릴 경우에 대비해 데이터베이스가 더 망가지지 않게 했다.
- S3 로 보관되지 못한 WAL 을 db-01 에서 db-02 로 복사해, S3 백업 복원이 끝나면 바로 재생할 수 있게 준비했다.
- wal-g 를 같은 메이저 버전의 최신 릴리스로 올린 뒤, 전체 백업이 이미 들어 있는 비어 있지 않은 데이터 디렉터리를 받아들이도록 wal-g 를 고쳐 빌드했다. 09:23 UTC 에 이 wal-g 를 db-02 에 써서 S3 에서 다시 받지 않고 증분 백업을 복원했다.
- S3 와 db-01 에서 옮겨 온 파일 시스템 양쪽에서 WAL 을 가져오는 래퍼 스크립트로 restore_command 를 설정하고 Postgres 를 standby 모드로 띄웠다. 11:00 UTC 부터 WAL 을 재생했다.
- 16:45 UTC 에 db-02 를 primary 데이터베이스로 승격하고, Synapse 시험 worker 를 띄워 새 WAL 이 S3 에 올라오는 것을 확인한 뒤 16:54 에 Synapse 를 시작했다. 18:00 에 모든 worker 가 안정됐다.
- WAL 재생이 느렸기 때문에, 없어진 복제본 대신 primary 데이터베이스에서 백업을 받도록 다시 설정했다. 03:26 UTC 에 새 증분 백업이 끝났다.
- db-01 에 NVMe 디스크 두 개를 RAID 배열과 LVM 볼륨 그룹에 넣었다. 배열 재구성 중 재부팅해 서버가 올라오지 않자 배열을 부팅할 수 있는 상태로 고쳤고, db-02 의 basebackup 을 복사해 db-01 을 다시 복제 상태로 두었다.
- 후속 조치
- 저장 공간이 늘어난 지금 pg_repack 을 돌려 빈 공간을 되찾는다.
- 증분 백업은 WAL 재생보다 훨씬 빨리 복원되므로 증분 백업을 더 자주 받도록 이미 바꿨다.
- Synapse 가 데이터를 저장하는 방식을 고쳐 앞으로 필요한 저장 공간을 크게 줄인다.
- 데이터베이스가 클러스터에서 맡은 역할에 따라 터미널 배경색을 바꾸는 방법을 검토한다. 데이터 디렉터리에 standby.signal 파일이 있는지 보고 primary 인지 secondary 인지 판단해 배경색을 바꾸는 안이 나왔다.
- rm 의 alias 처럼 위험한 명령을 감싸는 래퍼 스크립트를 두거나, primary 에서 secondary 로 base backup 을 시작하는 일 같은 작업을 자동화하는 방법을 논의했다.
- 복구 목표 시간을 줄인다. 더 쉽고 빠르게 복구할 수 있는 무료 인프라를 얻으려고 서비스 제공자들과 이야기하고 있다.
- 재해 복구 서버뿐 아니라 모든 서버의 wal-g 를 올렸고, 올린 wal-g 로 재해 복구 시험을 한 차례 했다. 재해 복구 서버와 운영 서버를 계속 같게 유지하는 방법은 아직 찾지 않았다.
- 다음 하드웨어 교체 때 ZFS 를 검토해, 엉뚱한 데이터베이스를 지우는 사고에서도 로컬 스냅숏으로 훨씬 빨리 복구할 수 있게 한다.
- 장애 대응 절차에 status.matrix.org 를 장애 중 공식 정보원으로 갱신하는 단계를 넣고, advocacy 팀과 함께 소셜 미디어도 계속 갱신한다.
- 교훈
이번 장애가 이렇게 힘들었던 데에는 데이터베이스가 너무 크다는 점이 크게 작용했다. 엉뚱한 서버에서 파괴적인 명령을 실행한 것이 결정적인 순간이었다. 하지만 팀은 개인을 탓하지 않고, 사람이 쓰는 도구와 절차를 고쳐 같은 일이 다시 일어날 가능성을 줄여야 한다고 보았다. 세 사람이 함께 작업했지만 급박한 상황에서는 실수를 잡지 못했다. db-01 은 primary, db-02 는 secondary 처럼 읽히지만 실제 역할은 서로 바뀔 수 있으므로, 뜻이 담긴 서버 이름은 혼란을 부른다. primary 와 secondary 를 모두 잃고도 데이터를 잃지 않은 것은 정기적으로 시험한 특정 시점 복구 절차 덕분이었다. 다만 복구 목표 시간은 줄여야 한다. 재해 복구 시험에서 찾은 wal-g 수정이 운영 서버에 빠져 있었으므로, 재해 복구 서버와 운영 서버를 같게 맞춰야 한다.
이 8건에서 가장 많이 나오는 구성 요소: 데이터베이스, 데이터베이스 복제. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.
원인별2
운영 실수7건
IL-0577로컬 데이터베이스를 초기화하려던 prisma migrate reset 이 운영 Neon PostgreSQL 데이터베이스를 초기화했다Ficilcom2026IL-0286Honeycomb 운영 Kafka 클러스터에서 브로커가 한꺼번에 사라져 토픽 파티션 여럿이 리더를 잃고 일부는 되살릴 수 없게 망가졌다Honeycomb2025IL-0576테스트 코드가 운영 BigQuery 데이터 웨어하우스의 데이터셋을 전부 지웠다CareNet2025IL-0242primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다IL-0565쿠키런: 킹덤의 CockroachDB 클러스터가 노드 24대 중 16대의 파일 시스템을 잃고 멈춰 게임이 36시간 넘게 점검에 들어갔다Devsisters2022IL-0332운영 데이터베이스의 모든 테이블이 truncate 돼 travis-ci.com 이 약 5.5시간 동안 멈췄다Travis CI2018IL-0506UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다UC Berkeley2017