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

사례7건

7건 중 1~5번째

  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. 증상

    2024년 9월 18일 데이터센터 전환을 앞두고 cookbook 으로 원형 복제를 설정하던 중, 21:28 UTC 쯤 엔지니어가 x1 섹션의 primary master 가 복제를 하지 않는 것을 orchestrator 에서 먼저 알아챘다. es6 섹션도 같은 상태였다.

    촉발

    다음 주 데이터센터 전환을 앞두고 cookbook 이 핵심 데이터베이스를 eqiad 에서 codfw 로 가는 한 방향 복제에서 원형 복제로 바꿨다. ROW 기반 복제를 쓰는 x1 섹션에 codfw 에서 eqiad 로 가는 복제를 설정하고 pt-heartbeat 를 시작하자, eqiad 서버에 없는 heartbeat 행을 갱신하려다 복제가 멈췄다.

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

    원형 복제의 일반 모니터링이 아직 켜지지 않아, 엔지니어가 orchestrator 에서 먼저 알아챘다. 복제 자동화가 처음 도는 날이라 평소보다 많은 사람이 데이터베이스 상태를 지켜보고 있었다. 사용자에게 영향을 준 eqiad 에서 codfw 로 가는 방향의 고장은 Icinga 알림이 잡았다. 온콜 호출을 받은 사람은 없었다.

    조치
    • SET GLOBAL Replication_wild_ignore_table='heartbeat.%'; 로 heartbeat 테이블 복제를 건너뛰게 해 복제가 다시 흐르게 했다.
    • es 호스트에는 원래 고장이 난 방향에 맞춰, binlog 기록을 끈 채 eqiad 서버마다 빠진 codfw heartbeat 행을 직접 넣었다. 명령은 set sql_log_bin=0; INSERT INTO heartbeat.heartbeat (server_id, shard, datacenter) VALUES (<codfw-server-id>, 'es6' [or 'es7'], 'codfw'); 였다. es6 에서 먼저 시험했고, es7 에는 원형 복제를 설정하기 전에 미리 넣었다.
    • x1 codfw 호스트마다 binlog 기록을 끈 채 빠진 heartbeat 행을 넣어, 임시로 걸어 둔 heartbeat 무시 설정을 대신했다. 명령은 set sql_log_bin=0; INSERT INTO heartbeat.heartbeat (server_id, shard, datacenter) VALUES (<eqiad-server-id>, 'x1', 'eqiad'); 였다.
    후속 조치
    • 같은 문제를 다시 겪지 않게 한다. 빠진 행을 찾으면 직접 넣는 방식을 유력하게 본다.
    • 유지 보수 뒤 복제가 의도대로 양방향으로 동작하는지 확인하는 상태 점검을 cookbook 에 넣는다.
    • 유지 보수를 다른 순서로, 또는 섹션마다 따로 돌릴 수 있는 옵션을 더한다.
    • Phabricator 를 마지막에 한 번만 갱신하지 않고 섹션마다 갱신한다.
    • 모니터링의 빈틈을 메운다.
    • x1 과 ROW 를 쓰는 읽기·쓰기 es 호스트에서 pt-heartbeat-wikimedia 가 자기 세션을 STATEMENT 로 쓰도록 고쳐 배포했다.
    교훈

    원형 복제는 평소 운영 방식이 아니어서 heartbeat 테이블을 지우는 첫 조치가 문제를 eqiad 에서 codfw 로 옮기기만 했다. TRUNCATE 는 DDL 이라 늘 STATEMENT 모드로 전달되므로 이번에는 DELETE 보다 나았겠지만, 더 위험해서 고려하지 않았다. ROW 기반 복제, pt-heartbeat, 서버마다 다른 heartbeat 테이블 행이 겹친 문제는 이전 유지 보수 기록에 없었다. 자동화의 점검은 설정 직후 한 번이 아니라 모든 작업이 끝난 뒤에도 복제 채널을 확인했어야 했다. Wikimedia 는 어느 호스트에서도 sql_skip_slave_counter 를 쓰지 않아 더 큰 문제나 데이터 손실을 피한 것을 잘된 점으로 꼽았다.

  3. 증상

    14:43 UTC 에 네트워크 스위치 하나가 ping 에 응답하지 않는다는 알림이 울리기 시작했다. 장애 동안 API 쿼리 성공률이 한때 75%까지 떨어졌고, 대시보드는 평소보다 최대 80배 느려졌다.

    촉발

    14:43 UTC 에 제어 플레인 데이터 센터의 네트워크 스위치 하나가 일부만 동작하는 상태에 빠졌다. LACP 와 BGP 는 계속 돌았지만 받은 패킷 일부를 전달하지 못했고, 스위치는 6분 뒤 스스로 회복했다.

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

    14:43 UTC 에 스위치가 ping 에 응답하지 않는다는 알림이 울리기 시작했다. 그 뒤 엔지니어들이 주 인증 데이터베이스가 과부하에 걸린 것을 확인했다.

    조치
    • SSL 인증서를 엣지로 보내는 속도와 메일 발송 같은 기능을 줄여 주 인증 데이터베이스가 늘어난 부하를 감당할 여유를 만들었다.
    • 읽기 복제본으로 처리할 수 있는 API 트래픽을 손으로 보조 데이터 센터로 돌렸다. API 가용성이 크게 나아졌다.
    • 대시보드 사용자의 불편이 커지자 인증 호출을 모두 주 데이터 센터로 되돌렸고, 성능이 떨어진 상태에서도 최대한 버티도록 주 데이터베이스를 계속 손봤다.
    • 21:20 UTC 에 첫 데이터베이스 복제본이 다시 만들어지자마자 자동으로 서비스에 투입됐고, 성능이 정상으로 돌아왔다.
    • 줄였던 서비스를 모두 다시 올려 밀린 비동기 처리가 따라잡게 했고, 한동안 지켜본 뒤 장애 종료를 선언했다.
    후속 조치
    • 새 primary 를 승격하면 데이터베이스 복제본을 모두 다시 만들어야 하는 클러스터 관리 시스템의 제약을 고친다. Cloudflare 는 글을 쓸 무렵 이 결함을 이미 고쳤다고 밝혔다.
    • 트래픽을 다른 데이터 센터로 돌려야 할 때 유연하게 대응하지 못하는 사용자 세션 시스템을 다시 설계한다.
    • 자동 복구 조치의 설정 값을 다시 검토한다. 적어도 데이터베이스 하나에서는 복제본을 primary 로 승격하는 절차를 그렇게 빨리 시작하지 않도록 장애 직후 설정을 바꿨다.
    • 이 장애에 얽힌 시스템마다 자기가 의존하는 시스템의 고장을 더 잘 견디도록 계속 조정한다.
    교훈

    모든 시스템에 이중화가 있었지만, 고장 난 시스템들은 완전히 멈추지 않고 성능이 떨어진 채로 동작했다. 그래서 뒤이어 일어난 일들을 미리 모델링하고 예상하기가 훨씬 어려웠다. 1분 만에 복제본을 승격하는 자동 조치는 Cloudflare 가 자랑하던 기능이었지만, 적어도 데이터베이스 하나에서는 그 조치가 원래 문제보다 더 큰 피해를 낼 수 있었다. 대부분의 범용 클러스터 관리 시스템은 비잔틴 장애를 견디는 프로토콜을 쓰지 않고 RAFT 처럼 단순한 프로토콜을 쓴다. 드문 장애 유형에 약하다고 알려진 단순한 프로토콜이 구현과 디버깅이 어려운 복잡한 프로토콜보다 안전한 경우가 많다고 Cloudflare 는 보았다.

  4. 증상

    s3 의 주 데이터베이스 마스터 db1075 가 응답하지 않아 s3 의 모든 위키가 쓰기를 받지 못하고 읽기 전용이 됐다. 레플리카는 모두 살아 있어 읽기는 정상이었다. Icinga 가 IRC 에 HOST DOWN 을 알렸고 사용자들이 #wikimedia-operations 에 문제를 알렸다.

    촉발

    db1075 의 RAID 백업 배터리인 BBU 가 고장 나면서 호스트가 완전히 멈췄다.

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

    db1075 의 HOST DOWN 이 보고되면서 문제가 분명해졌다. 하지만 HOST DOWN 알림은 IRC 로만 가고 호출을 보내지 않았다. 알림은 IRC 와 호출로 보내졌고, 첫 SMS 호출은 18:56 UTC 에 나갔다. 사용자들도 #wikimedia-operations 에 문제를 알렸고, 자원봉사자와 직원은 알림보다 먼저 장애를 알아챘다.

    조치
    • BBU 문제이고 호스트가 응답하지 않는 것을 확인한 뒤 19:02 UTC 에 idrac 으로 호스트를 재부팅했다.
    • MySQL 이 19:03 에 InnoDB 복구를 시작해 19:06 에 마쳤다.
    • 19:07 에 db1075 의 read_only=ON 을 직접 풀었다.
    • 19:16 에 puppet 을 다시 직접 돌려 성공했고, pt-heartbeat 가 시작되면서 장애가 끝났다.
    후속 조치
    • 마스터를 쓸 수 없을 때 레플리카를 옮기는 스크립트를 만들거나 고친다. 이번에는 필요하지 않았지만 앞으로의 장애에서는 필요할 수 있다.
    • MediaWiki 의 heartbeat 방식을 고친다. pt-heartbeat 가 슈퍼유저를 쓰지 않게 하고, 단일 장애점을 없애고, puppet 없이 실제 마스터로 자동 전환되게 한다.
    • 이미 BBU 문제를 겪은 같은 배치의 호스트들을 어떻게 할지 정한다.
    • db1075 에 쓸 새 BBU 를 산다.
    • db1075 를 마스터에서 뺀다.
    • 읽기 전용이거나 마스터를 쓸 수 없는 동안 MediaWiki 가 쏟아내는 스팸을 해결한다.
    • 주 데이터베이스 마스터가 HOST DOWN 일 때 호출이 가게 한다.
    • Netbox 에서 하드웨어 오류를 더 잘 추적한다.
    교훈

    BBU 고장이 호스트 전체를 멈추게 해서는 안 됐지만 HP 호스트에서는 전에도 그런 일이 있었다. puppet 으로 띄우는 pt-heartbeat 와 아직 다 뜨지 않은 MySQL 사이의 경합으로 수동 puppet 실행이 실패했는데 아무도 알아채지 못했다. 그 때문에 모든 것이 올라온 뒤에도 복제 지연이 보고돼 장애가 8분 더 길어졌다. 알림은 잘 동작했고 idrac 으로 재부팅한 것도 통했다. 호스트가 깨끗하게 돌아오지 않았다면 새 레플리카를 마스터로 올리는 장애 조치를 손으로 해야 했다.

  5. 증상

    WORKER_JOB_ERROR 오류가 늘었다는 신고가 들어왔다. 이 오류는 파일을 받아 Assembly 를 조율하는 Uploader 와 인코딩을 실행하는 Drone 사이의 통신에 문제가 있다는 뜻이다.

    촉발

    HLS 트래픽이 가파르게 늘었고, 같은 양을 더 빨리 처리하려고 Drone 을 더 많이 띄울 수 있게 되면서 중앙 노드가 바빠졌다. 이를 설명할 코드나 인프라 변경은 찾지 못했고, 이 두 변화가 겹쳐 Redis 병목이 드러났다.

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

    WORKER_JOB_ERROR 오류가 늘었다는 신고가 들어와 문제를 알았다. 모니터링은 Redis 명령 수, 연결 수, 스왑 사용량을 보고 있었지만 대역폭은 보지 않아 포화를 잡지 못했다.

    조치
    • 잃은 메시지를 다시 넣고 하트비트 감시를 느슨하게 하는 임시 조치를 했다. 증상만 막는 조치였고 Assembly 실행 시간이 늘었다.
    • 놓친 Redis 패치가 있는지 확인하고 Redis 를 최신 안정 버전으로 올렸다.
    • 대역폭 제한에 걸린 것을 알고 Redis 클러스터를 두 배인 여섯 대로 늘렸다. 곧 다시 포화되자 세 대를 더 붙였지만 상황은 더 나빠졌다.
    • 읽기 replica 를 하나로 줄였다. 트래픽과 실패율이 바로 떨어졌고 Assembly 가 다시 빨라졌다.
    • 리전마다 Drone 확장에 상한을 두었다. 큰 배치 가져오기는 시간이 더 걸릴 수 있지만 시스템은 안정을 유지한다고 밝혔다.
    후속 조치
    • 포화 수준에 다시 가까워지면 알 수 있도록 로그와 알림을 더 둔다.
    • Redis 노드와 Drone, Uploader 사이의 트래픽을 한곳에 몰리지 않게 나눌 방법을 찾는다.
    • 이미 하고 있는 리전별 샤딩에 더해 Uploader 별 샤딩을 추가한다.
    교훈

    Redis 노드를 늘린 것은 잘못된 판단이었다. 모든 쓰기를 리더 한 대가 받는 구조에서는 노드가 늘수록 리더가 복제로 내보낼 트래픽이 늘어, 더 많은 트래픽을 감당하려면 노드를 오히려 줄여야 했다. 잃은 데이터를 다시 넣을 수 있으므로 팀은 복제가 꼭 필요한지 다시 따져 봤다. 지표는 이미 많이 지켜보고 있었지만 알림을 제대로 걸 수 있는 지표는 몇 개뿐이어서, 고른 지표가 모자랐을 수 있다. 대역폭도 빠져 있었다. 쓰기를 받는 Redis 노드가 하나뿐이면 replica 를 줄이거나 노드를 키워도 시간을 조금 벌 뿐이고, 성장을 버티려면 트래픽을 나눠야 한다.

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

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

원인별6

운영 실수1건

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

설정 변경2건

  1. IL-0392 데이터센터 전환을 앞두고 원형 복제를 설정하던 중 Wikimedia x1 섹션의 eqiad·codfw 사이 MariaDB 복제가 6분 멈췄다
  2. IL-0215 Twilio 결제 시스템의 Redis 가 잔액 데이터를 잃고 읽기 전용이 되어 자동 충전 고객의 신용카드가 반복 결제됐다
    Twilio2013

DNS·네트워크 장애1건

  1. IL-0240 인증 데이터베이스가 과부하에 걸려 Cloudflare API 와 대시보드가 6시간 33분 동안 실패하고 느려졌다

하드웨어 고장1건

  1. IL-0323 s3 주 데이터베이스 마스터 db1075 가 완전히 멈춰 s3 위키 전체가 읽기 전용이 됐다

용량 한계1건

  1. IL-0296 Transloadit 의 us-east 리전 Redis 클러스터가 대역폭에 막혀 작업과 하트비트를 잃었다

소프트웨어 결함1건

조직
  1. IL-0303 GitHub 의 새 3노드 MySQL 클러스터에서 자동 페일오버가 잘못 일어나고 클러스터 상태가 갈라져 GitHub.com 이 두 번 멈췄다
    GitHub2012