Incident Library
  • 원인
  • 구성 요소
  • 증상
  • 기여 요인
  • 사례

사례8건

8건 중 3~7번째

  1. 증상

    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 수정이 운영 서버에 빠져 있었으므로, 재해 복구 서버와 운영 서버를 같게 맞춰야 한다.

  2. 증상

    2019년 12월 어느 금요일 저녁, 내부 모니터링 도구가 시스템에서 5xx 오류가 평소보다 훨씬 많이 난다고 알렸다. SRE 팀은 주요 애플리케이션인 API 가 RDS MySQL 데이터베이스에 접속하지 못한다는 것을 알아챘다. 원인을 파악하기도 전에 애플리케이션이 스스로 살아났고 알림이 멈췄다.

    촉발

    API 애플리케이션의 RDS MySQL 마스터 인스턴스가 Multi-AZ 장애 조치를 거쳐 다른 AZ 의 대기 인스턴스로 넘어갔다.

    원인
    구성 요소
    기여 요인
    –
    감지

    내부 모니터링 도구가 5xx 오류가 평소보다 훨씬 많다고 알렸다. 복제 실패는 그 뒤 다른 알림으로 왔다. 데이터 손실은 다음 날 데이터베이스와 애플리케이션 트레이스 로그를 맞춰 보다가 찾았다.

    조치
    • 오래된 데이터가 사용자에게 보이지 않도록 복제본으로 가던 트래픽을 모두 마스터 인스턴스로 옮겼다.
    • 새 장비의 전체 스냅샷이 끝나기를 여섯 시간 기다린 뒤 복제본 다섯 대를 다시 만들고 트래픽을 복제본으로 되돌렸다.
    • 바이너리 로그에서 정확한 쿼리를 찾고 애플리케이션 트레이스 로그로 사건 순서를 따라가 8시간 회의 끝에 RDS 에 저장된 데이터를 바로잡았다.
    • 마스터 인스턴스의 innodb_flush_log_at_trx_commit 값을 2 에서 1 로 바꿨다.
    교훈

    잘못 설정한 변수 하나 때문에 Razorpay 는 처음부터 데이터 손실 위험을 안고 있었다. 팀은 장애를 끝까지 파고들어 근본 원인을 찾았고, 그 과정에서 RDS 가 Multi-AZ 구성과 장애 조치를 어떻게 다루는지 많이 배웠다.

  3. 증상

    2017년 1월 31일 19:00 UTC 무렵 GitLab.com 의 데이터베이스 부하가 올랐고, 많은 사용자가 이슈와 머지 리퀘스트에 댓글을 달지 못했다. 스팸으로 의심되는 부하였고, 잡는 데 몇 시간이 걸렸다.

    촉발

    1월 31일 23:00 UTC 무렵 데이터베이스 부하로 세컨더리의 복제가 멈췄고, 세컨더리를 다시 맞추던 엔지니어가 세컨더리가 아니라 프라이머리에서 PostgreSQL 데이터 디렉터리를 지웠다.

    원인
    구성 요소
    기여 요인
    감지

    프라이머리에서 데이터 디렉터리를 지운 엔지니어가 스스로 실수를 알아채고 1~2초 만에 삭제를 멈췄다.

    조치
    • pg_basebackup 이 복제 연결이 모자라다고 알리자 max_wal_senders 를 기본값 3 에서 32 로 잠시 올렸고, PostgreSQL 이 재시작을 거부하자 max_connections 를 8000 에서 2000 으로 낮췄다.
    • 엔지니어가 프라이머리에서 데이터를 지우고 있다는 것을 알아채고 1~2초 만에 삭제 작업을 멈췄지만, 약 300 GB 가 이미 지워졌다.
    • 데이터 손실을 가장 줄일 수 있는 유일한 방법으로 장애 6시간 전에 만든 LVM 스냅샷을 쓰기로 하고, 스테이징의 PostgreSQL 데이터 디렉터리를 약 18시간 동안 운영으로 복사해 복원했다.
    • 웹훅 삭제를 거치지 않은 별도 스테이징 데이터베이스를 LVM 스냅샷으로 띄워 웹훅 테이블의 SQL 덤프를 만들고, 복원한 GitLab.com 데이터베이스에 가져왔다.
    • 장애 전에 쓰였을 수 있는 ID 를 다시 쓰지 않도록 모든 데이터베이스 시퀀스를 100,000 씩 올렸다.
    • GitLab.com 을 차례로 다시 열었다.
    • 17:20 UTC 이후에 만든 프로젝트는 NFS 클러스터에 남은 Git 저장소로 가장 기본적인 형태만 복원했고, 이슈와 머지 리퀘스트 같은 데이터는 되살리지 못했다.
    후속 조치
    • 모든 호스트의 셸 프롬프트인 PS1 을 고쳐 호스트와 환경을 더 분명히 구분한다.
    • 백업을 Prometheus 로 모니터링한다.
    • PostgreSQL 의 max_connections 를 알맞은 값으로 둔다.
    • PostgreSQL 의 시점 복구와 연속 아카이빙을 검토한다.
    • 운영 데이터베이스의 LVM 스냅샷을 매시간 뜬다.
    • 운영 데이터베이스에 Azure 디스크 스냅샷을 켠다.
    • 스테이징을 ARM 환경으로 옮긴다.
    • 운영 복제본을 되살린다.
    • PostgreSQL 백업 복원을 자동으로 시험한다.
    • PostgreSQL 복제 문서와 런북을 고친다.
    • PostgreSQL 백업에 pgbarman 을 쓸지 검토한다.
    • 데이터베이스 백업과 실시간 복제에 WAL-E 를 쓸지 검토한다.
    • 스트리밍 방식의 데이터베이스 복원을 만든다.
    • 데이터 내구성을 책임질 담당자를 정한다.
    • 세컨더리를 여러 대 두고 부하를 나누도록 pgpool-II 3.6.1 을 묶고 PostgreSQL 연결 풀링과 부하 분산을 구성한다.
    • 개발자가 데이터베이스 마이그레이션을 시험할 수 있도록 스테이징에서 Rails 마이그레이션을 실행하고 되돌리는 도구를 만든다.
    • 데이터베이스뿐 아니라 GitLab.com 인프라 전체의 복구 절차를 만들고 그 절차의 담당자를 정한다.
    • 공개 백업 모니터링 대시보드를 만들었고, 지금은 pg_dump 백업 데이터만 있지만 데이터를 더 넣는다.
    • 어뷰징 신고와 대응 시스템을 고쳐 스팸 신고로 사용자를 지울 때 데이터를 바로 영구 삭제하지 않게 한다.
    • 앞으로 공개하는 장애 문서에서는 엔지니어의 이름을 가린다.
    교훈

    GitLab 은 장애를 GitLab.com 이 약 18시간 멈춘 문제와 복원이 18시간 넘게 걸린 문제로 나눠 5 Whys 로 분석했다. 세컨더리를 다시 만드는 작업은 자동화되지도 제대로 문서화되지도 않아 손으로 해야 했다. 표준 백업은 PostgreSQL 버전이 맞지 않아 조용히 실패했고, 실패 알림 메일은 DMARC 서명이 없어 거부됐다. 다른 백업 절차를 믿고 Azure 디스크 스냅샷을 켜지 않았다. 백업 절차를 맡은 사람이 없어 아무도 정기적으로 시험하지 않았다. GitLab 은 운영 엔지니어가 특정 명령을 못 쓰게 막는 것보다 재해 복구를 고치고 지금 어느 호스트를 쓰는지 더 분명히 보이게 하는 데 집중하기로 했다. rm 을 더 안전한 별칭으로 바꾸면 실수로 지우는 것은 막지만 디스크 손상처럼 데이터를 잃는 다른 경우는 막지 못한다. 이상적인 환경은 실수해도 영향이 거의 없이 빠르게 복구할 수 있는 곳이고, 그러려면 복구 절차를 정기적으로 해 보고 변경을 쉽게 시험하고 되돌릴 수 있어야 한다.

  4. 증상

    2016년 12월 27일 오후 3시, 결제 시스템 모듈을 개발하던 개발자의 실수로 빌링 데이터베이스의 주요 테이블 9개가 DROP됐다. 모니터링 시스템에 빨간불이 들어오고 장애 알림이 쏟아졌으며, 배달의민족 결제가 잠시 중단됐다.

    촉발

    결제 시스템 모듈을 개발하던 개발자가 실수로 빌링 데이터베이스의 주요 테이블 9개를 DROP했다.

    원인
    구성 요소
    기여 요인
    –
    감지

    모니터링 시스템에 빨간불이 들어오고 장애 알림이 쏟아져 알았다.

    조치
    • 애플리케이션이 임시 데이터베이스를 보도록 고쳐 결제 중단 시간을 줄였다.
    • 매일 새벽 6시 mysqldump로 만든 Full Backup 파일을 풀어 DB에 실행해, 빌링 시스템을 연 때부터 새벽 6시까지의 데이터를 복구했다.
    • mysqlbinlog로 binary log에서 2016-12-27 06:00:00 부터 15:00:00 까지의 이벤트를 restore.log에 뽑고, 9개 테이블만 남기도록 고친 뒤 mysql에 실행해 나머지 데이터를 복구했다.
    후속 조치
    • 빌링 데이터베이스는 장애 때 복구 과정을 단순하게 하려고 XtraBackup으로 증분 백업 없이 전체 백업을 한다.
    교훈

    장애를 관리할 지점은 많다. 데이터베이스의 서비스용 계정과 관리용 계정을 나누는 것, 쿼리를 보내기 전에 한 번 더 확인하는 것, rm -rf를 막는 것이 그 예다. 전체 백업과 binary log가 없었다면 복구할 수 없었으므로, 장애 때 전체를 복구할 지점이 있는지 점검해야 한다. 데이터가 수십에서 수백 GB에 이르면 mysqldump로는 백업과 복구에 너무 오래 걸린다.

  5. 증상

    복제 오류를 고치려고 mysql2 의 데이터베이스를 하나씩 지우던 중, 몇 분 뒤 mysql1 에서도 데이터베이스가 지워지고 있는 것을 알아챘다. 지워진 데이터베이스들은 복원될 때까지 쓸 수 없었다.

    촉발

    7월 30일 오후 4시 PDT 에 팀은 복제 오류를 고치려고 mysql2 를 다시 적재하기로 했다. 슬레이브 복제를 멈춘 뒤 mysql2 의 데이터베이스를 하나 지울 때마다 잠깐씩 쉬면서 지웠다.

    원인
    구성 요소
    기여 요인
    감지

    데이터베이스를 지우는 스크립트가 하나를 지울 때마다 짧게 쉬었기 때문에, 시작하고 몇 분 뒤 mysql1 에서도 데이터베이스가 지워지고 있는 것을 알아챘다.

    조치
    • mysql1 에서도 데이터베이스가 지워지는 것을 보고 스크립트를 멈췄다.
    • 곧바로 지워진 데이터베이스의 복원을 시작했다.
    • 인덱스와 외래 키 문제로 복원이 실패한 데이터베이스는 약 200GB 백업 파일에서 따로 꺼내 복원했고, 아직 복원하지 못한 나머지 데이터베이스도 다시 꺼내 복원했다.
    • 백업은 mysql1 보다 뒤처진 mysql2 에서 뜬 것이어서, 17일부터 30일까지의 바이너리 로그로 대부분의 데이터를 되살렸다. 15일과 17일 사이의 데이터 일부는 되살리지 못했다.
    후속 조치
    • 처음 문제였던 복제 오류를 줄이려고 행 기반 복제를 포함해 더 나은 복제 방식을 검토한다.
    • 바이너리 로그를 별도 서버에 백업으로 보관한다.
    • 전체 복원이 훨씬 빨라지도록 Percona XtraBackup 으로 백업한다.
    • 논리 백업 속도를 높이려고 mysql-zrm 대신 mydumper 를 쓴다.
    • 복잡한 시스템의 문서와 교육을 손본다.
    • 백업 절차의 하나로 예비 서버에서 전체 복원을 정기적으로 시험한다.
    • 복원에 걸리는 예상 시간을 더 정확하게 잡는다.
    • 호스팅하는 데이터베이스를 정기적으로 점검한다. 시험용 데이터베이스와 100GB 넘게 불어난 데이터베이스 여러 개가 이번 복원을 크게 늦췄다.
    • 더 크고 성능이 좋은 MySQL 클러스터로 옮긴다. 이 이전은 장애 전부터 계획돼 있었다.
    • 장애와 유지보수, 자원 사용량과 경고 신호를 대시보드로 커뮤니티에 더 잘 알리는 정책과 수단을 만든다.
    • 핵심 서비스의 용량 한계를 정하고 감시한다.
    • 핵심 서비스에 이중화와 복원 절차를 갖추고 직원 훈련을 한다.
    • 핵심 서비스를 더 많이 Chef 로 옮긴다.
    • 문서, 정책, 성능 지표를 주기적으로 검토한다.
    • 커뮤니티의 전문 지식으로 연구소의 역량을 보완할 더 나은 방법을 찾는다.
    • 가장 낡은 인프라를 교체하고 가장 심한 기술 부채를 해소할 기금을 모은다.
    교훈

    Open Source Lab 은 이번 장애에서 MySQL 클러스터를 운영하는 방식과 연구소 전체가 자원을 계획하고 관리하는 방식 모두에서 교훈을 얻었다고 적었다. 디스크 공간을 아끼려고 백업을 강하게 압축한 선택은 전체 복원이 드물었던 그동안에는 괜찮았지만 이번 복원을 크게 늦췄다. 더 많은 프로젝트를 받으려고 자원을 빠듯하게 나눠 써 왔고, 그 대가로 백업을 압축하고 바라던 것보다 이중화를 덜 갖췄다. 장애가 길었던 것과 진행 상황을 알리지 못한 것은 받아들일 수 없는 일이었다고 밝혔다.

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

이 8건에서 가장 많이 나오는 구성 요소: 데이터베이스 백업과 스냅숏, 데이터베이스. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.

원인별4

소프트웨어 결함1건

조직
  1. IL-0281 PostHog US 리전 Logs 제품의 ClickHouse 클러스터가 S3 에 둔 로그 데이터를 거의 다 지워 3일보다 오래된 로그가 사라졌다

배포 결함1건

  1. IL-0380 AWS us-east-1 의 InfluxDB Cloud 클러스터에서 압축 중에 일부 데이터가 빠져 쿼리에 나오지 않았다

운영 실수5건

  1. IL-0242 primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다
  2. IL-0218 복제를 다시 맞추다 PostgreSQL 프라이머리의 데이터 디렉터리가 지워지고 백업이 작동하지 않아 GitLab.com 이 약 18시간 멈추고 데이터를 잃었다
    GitLab2017
  3. IL-0573 개발자의 실수로 MariaDB 빌링 데이터베이스의 주요 테이블 9개가 DROP되어 배달의민족 결제가 잠시 멈췄다
  4. IL-0319 mysql2 를 다시 적재하려고 데이터베이스를 지우는 동안 mysql1 의 데이터베이스도 함께 지워졌다
  5. IL-0538 사쿠라 클라우드 스토리지의 일괄 삭제 작업이 고객이 쓰던 디스크 53개를 지웠고 되살리지 못했다

설정 변경1건

기여 요인
–
조직
  1. IL-0331 RDS MySQL Multi-AZ 장애 조치 뒤 읽기 복제본 다섯 대의 복제가 모두 깨지고 데이터 5초 분량이 사라졌다