Incident Library
적용된 필터 원인운영 실수증상데이터 유실모두 풀기검색어 없이 필터만 적용한 목록입니다. 최근 사례부터입니다.
추천 필터 검색하면 검색어에서 찾은 분류 값이 여기에 나옵니다.

그래프

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

사례14건

14건 중 4~8번째

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

    Ballast 파일을 만드는 스크립트를 실행한 직후 에러율, 데이터베이스 연결, Health Check 알람이 수백 건 한꺼번에 울렸다. 알람 메시지를 읽기도 전에 다음 알람이 쏟아졌고, 정상적인 게임 이용이 어렵다고 본 팀은 16시 52분에 긴급 점검을 시작했다.

    촉발

    디스크가 가득 찰 때를 대비해 Ballast 파일을 만드는 스크립트를 실행했는데, 파일 경로가 잘못 설정돼 Block Device를 가리켰다. 이 명령에는 대상 경로에 파일이 이미 있는지 확인하는 안전장치가 없어 파티션을 그대로 덮어썼다.

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

    작업 직후 미리 설정해 둔 메트릭으로 알았다. 에러율, 데이터베이스 연결, Health Check 알람이 수백 건 동시에 울렸다. 2차 장애는 새벽 2시 42분 데이터베이스의 모든 쿼리와 요청이 실패하는 것을 관측하고 알았다.

    조치
    • 정상적인 게임 이용이 어렵다고 보고 운영팀에 요청해 16시 52분부터 긴급 점검을 시작했다.
    • SST 파일의 구조를 해독하고, 장애 다음날 오전 4시쯤 Spark로 SST 파일 내용을 CSV 텍스트 파일로 추출하는 샘플 코드를 작성했다. 예상 처리 시간이 약 24시간으로 너무 길어 CockroachDB CLI 소스코드를 필요한 작업에 맞게 고쳐 10배 이상 빠르게 했다.
    • 오전 10시 30분쯤부터 마지막까지 살아남은 노드 7대의 SST 파일 약 7.5TB를 CSV로 변환하며 나오는 에러를 고치는 작업을 반복했다.
    • IMPORT INTO는 실행하는 동안 테이블을 오프라인으로 만들고, 키 충돌이 하나라도 나면 전부 Revert한다. 이 제약을 확인한 팀은 이튿날 오후 12시부터 중복을 없애 데이터베이스에 바로 넣을 수 있는 하나의 큰 CSV 파일로 병합하는 Spark 코드를 작성했다.
    • 스토리지가 넉넉한 새 데이터베이스 클러스터에 데이터를 넣기 시작했고, 시간이 너무 오래 걸릴 것으로 보여 유저마다 가장 최근 스냅샷 1개와 가장 최근 저널 300개만 복원하기로 범위를 좁혔다.
    • 서비스 재개에 쓸 깨끗한 클러스터를 따로 준비해 저널, 스냅샷, 유저 테이블 외의 테이블은 백업에서 복원했다. 저널과 스냅샷 테이블은 전처리한 CSV에서 Import했고, 유저 테이블은 서버 로직으로 다시 만들어 오후 8시에 모든 작업을 마쳤다.
    • 일부 유저를 샘플링해 분석 로그에 기록된 상태와 최종값이 같은지 검증했고, 모든 유저의 스냅샷 리플레이가 정상적으로 동작하는지 검증했다.
    • 이튿날 23시 37분, 30시간 50분 만에 서비스를 다시 열었다.
    • 부하로 장애가 난 DevPlay 플랫폼의 AWS RDS 데이터베이스를 일시적인 순단을 감수하고 Failover해 정상화했다.
    • 공식 백업과 복원 기능으로 데이터를 옮겨 기존 m5d 인스턴스를 더 많은 대수로 쓰는 세 번째 데이터베이스 클러스터를 구성했다. CockroachDB 서포트 엔지니어들과 논의해 TPC-C 벤치마크에 쓰는 것과 비슷한 설정값을 적용해 데이터를 옮기는 시간을 줄였다.
    • 오전 8시 30분, 총 점검 시간 36시간 29분 만에 임시점검을 끝냈다.
    후속 조치
    • 프로덕션 인프라 작업은 아무리 사소해도 반드시 작업자 2명 이상이 참여하도록 규칙을 바꿨다.
    • 작업에 쓸 스크립트와 작업 시나리오를 엄격하게 작성해 미리 팀 안에 공유하고 리뷰를 받도록 했다.
    교훈

    숙련된 작업자도 사람인 이상 실수할 수 있으므로, 실수를 막으려면 사람에게 기대지 말고 시스템을 갖춰야 한다. 복구할 수 있었던 바탕은 세 가지였다. 작업자 개인을 탓하지 않고 대응 과정을 기록해 액션 아이템을 정하는 장애 대응 원칙이 있었고, 과거 장애 경험이 기록으로 쌓여 있었으며, 인프라 운영과 데이터 분석을 맡는 팀이 한 조직에 있었다.

  3. 증상

    2018년 3월 13일 12:14 UTC 에 travis-ci.com 의 운영 데이터베이스에서 모든 테이블이 비워졌고, 곧바로 알림이 울렸다. 12:34 UTC 에 팀은 travis-ci.com 의 대규모 장애를 공지했다. 12:45 UTC 에는 고객들이 계정 데이터가 없어졌다고 신고했고, 팀도 같은 증상을 확인했다.

    촉발

    2018년 3월 13일 12:04 UTC 에 개발자가 tmux 세션의 오래된 터미널 창에서 테스트 묶음을 돌렸다. 이 셸에는 여러 날 전 운영 데이터를 살펴볼 때 설정한 DATABASEURL 환경 변수가 운영 데이터베이스를 가리킨 채 남아 있었다. 그래서 Database Cleaner gem 이 운영 데이터베이스에 truncate 쿼리를 실행했다.

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

    데이터베이스가 비워진 직후 알림이 울렸다. 12:45 UTC 에는 고객들도 계정 데이터가 없어졌다고 신고했다.

    조치
    • 시스템의 모든 애플리케이션을 내렸다.
    • 그날 아침에 찍은 스냅샷에서 대체 데이터베이스를 만들었다.
    • 17:06 UTC 에 데이터베이스를 플랫폼에 다시 연결해 www.travis-ci.com 과 api 를 올렸다.
    • 빌드 적체를 처리했고, 20:59 UTC 에 적체가 모두 풀려 시스템이 정상으로 돌아왔다.
    • 다른 사용자로 로그인되는 문제를 막으려고 3월 14일 수요일 14:22 UTC 까지 영향받은 토큰을 모두 폐기했다.
    • 영향받은 고객에게 travis-ci.com 에 저장한 자격 증명을 암호화 여부와 상관없이 교체하고, 빌드 로그에 민감한 정보가 없는지 확인하라고 권했다.
    후속 조치
    • 데이터베이스에서 truncate 권한을 거둬 테이블을 truncate 할 수 없게 했다.
    • 내부 spechelpers 가 DATABASEURL 환경 변수를 검사하도록 고쳤다.
    • 개발 도구에서 DATABASE_URL 이 설정돼 있으면 셸 프롬프트가 경고하게 했다.
    • 원격 DATABASE_URL 을 실수로 쓰는 일을 막도록 Database Cleaner gem 에 Pull Request 를 보냈다.
    • 운영 데이터로 시험해야 할 때 팔로워 데이터베이스를 쉽게 찾아 접속하도록 별칭을 만들었다.
    • 이런 상황에서 복구에 드는 시간과 수동 단계를 줄이려고 데이터베이스 장애 조치와 유지 보수를 자동화했다.
    교훈

    원인을 이해하고 서둘러 바로잡는 동안 사용자용 애플리케이션을 켜 둔 것이 실수였다. 이 때문에 사용자 id 가 겹쳐 만들어졌다. 일부 알림을 다시 켜지 않아 팀은 cron 스케줄러가 멈춘 것을 몰랐다. 도구와 절차 때문에 읽기 전용 팔로워에 접속하기가 어려웠고, 그래서 운영 데이터를 살펴볼 때 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 지름길이 흔해졌다. 가장 경험 많은 개발자도 실수로 큰 장애를 낼 수 있다는 점을 다시 확인했다. 대응하면서 놓친 단계가 있었고, 그 단계를 밟았다면 문제가 줄었을 것이다.

  4. 증상

    Mackerel 을 온프레미스 데이터 센터에서 AWS 로 옮긴 뒤 애플리케이션 동작을 확인하다가, 특정 시간대의 데이터가 사라진 것을 발견했다. 2017년 8월 7일 14시 30분부터 15시 30분 사이에 PostgreSQL 서버에서 갱신된 데이터가 없었다.

    촉발

    새 마스터인 db001 로 접속을 바꾸고 한 시간 뒤 db001 과 네트워크 통신이 끊겼고, 복제가 멈춰 있던 스탠바이 서버 db002 로 의도하지 않은 장애 조치가 일어났다.

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

    마이그레이션 작업 뒤 애플리케이션 동작을 확인하던 중 특정 시간대의 데이터가 사라진 것을 발견했다.

    조치
    • db001 에 남은 테이블 데이터로 손으로 복구할 수 있는지 검토했지만, 이미 많은 데이터가 갱신되어 테이블 사이 정합성을 지키며 db002 에 반영하기 어렵다고 보고 포기했다.
    • 점검 전 베이스 백업, db001 에 남은 WAL, 백업 서버로 보내 둔 WAL 아카이브로 PostgreSQL 의 PITR 복구를 시도했지만, 갈라진 두 타임라인을 이어서 복구할 수 없어 포기했다.
    • 장애 조치 직후 db002 를 마스터로 하고 새 슬레이브 서버 db003 을 만들어 db002 와 db003 으로 이중화했다.
    • 장애가 난 db001 은 조사용으로 점검 상태에 남겨 두었다.
    • db002 에서 db001 과 같은 문제가 나지 않는지 일정 시간 지켜본 뒤 마이그레이션 점검을 마쳤다.
    후속 조치
    • PostgreSQL 레플리카 생성 스크립트가 recovery.conf 에 recovery_target_timeline = 'latest' 를 기본으로 넣도록 바꾼다.
    • Serverspec 같은 서버 구성 관리 테스트 도구에 recovery.conf 안에 recovery_target_timeline = 'latest' 문자열이 있는지 확인하는 항목을 더한다.
    교훈

    Hatena 는 이 문제가 평소 운영에서는 일어나지 않지만, PostgreSQL 버전을 올리거나 다른 리전으로 옮길 때 같은 구성을 다시 쓸 수 있다고 보았다. recovery_target_timeline = 'latest' 는 평소 스탠바이 서버에 두어도 문제가 없으므로, 사람이 빠뜨릴 여지를 없애려고 슬레이브를 만들 때 기본으로 넣도록 운영을 바꿨다. 이 설정을 쓰려는 사람은 자기 구성에서 문제가 없는지 먼저 확인하라고 덧붙였다.

  5. 증상

    새벽 1시쯤 DataHub 에 로그인하려는 사용자가 프록시 오류를 받았다. hub 파드는 떠 있었지만 로그가 sqlite 오류로 가득했다.

    촉발

    엔지니어가 앞서 난 400 오류를 디버깅하려고 hub 파드 안에서 sqlite3 를 다시 열고, 데이터베이스를 ATTACH 하지 않은 채 .save jupyterhub.sqlite 를 실행했다. 이 명령이 JupyterHub 데이터베이스 파일을 빈 데이터베이스로 덮어썼다.

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

    새벽 1시쯤 DataHub 가 다운됐다는 신고가 들어왔다. 1:12 에는 한 사용자가 로그인은 되지만 노트북이 모두 사라졌다고 알렸다.

    조치
    • hub 파드의 replicas 를 0으로 줄여 hub 를 내렸다.
    • 쿠버네티스 PVC 를 사용자 id 와 다시 짝지어 JupyterHub sqlite 데이터베이스를 다시 만들었다. PVC 이름에 정리된 사용자 이름과 사용자 id 가 들어 있어서 할 수 있었다.
    • 처음 넣은 가짜 cookie_id 값을 null 로 바꾸고, sqlite 파일을 DataHub 에 다시 붙였다.
    • 모든 사용자를 로그아웃시키려고 hub 파드의 /srv/jupyterhub/jupyterhub_cookie_secret 을 바꿨다. 끝 근처 한 글자를 바꿨을 때는 효과가 없었고, 앞쪽 한 글자를 바꾸고 재시작하자 모두 로그아웃됐다.
    • 사용자 파드를 모두 지워 모든 사용자를 확실히 로그아웃시켰다.
    • 모든 사용자의 cookie_id 를 채우는 스크립트를 돌렸고, 메모리에 남은 오래된 캐시를 없애려고 hub 를 재시작한 뒤 사용자 파드를 다시 모두 지웠다.
    • 이름에 '.' 이 들어간 새 사용자를 지우고, 이름에 '-' 가 있는 모든 사용자의 '-' 를 '.' 으로 바꾸는 스크립트를 돌렸다.
    후속 조치
    • JupyterHub 가 cookie secret 의 일정 길이만 쓰고 나머지를 버려서, 로그아웃시키려고 값을 바꿀 때 혼란이 생긴다는 문제를 JupyterHub 에 보고한다.
    • users 테이블의 cookie_id 열에 UNIQUE 와 NOT NULL 제약을 두자고 JupyterHub 에 제안한다.
    • 데이터베이스가 지워져도 파드와 PVC 이름이 그대로 남도록, 이름에 사용자 id 대신 사용자 이름 해시를 쓰는 기능을 KubeSpawner 에 요청한다.
    • 사용자 테이블의 id 를 Google 인증이 주는 'id' 와 같게 두어, 처음 로그인한 때와 상관없이 사용자 id 가 바뀌지 않게 하는 기능을 OAuthenticator 에 요청한다.
    • 관리형 MySQL 서비스인 Google Cloud SQL 로 옮긴다.
    • 데이터베이스를 정기적으로 백업하고 그 백업을 시험한다.
    • 해야 할 일과 하지 말아야 할 일을 담은 운영 FAQ 를 쓰기 시작한다.
    • 모니터링과 호출 체계를 개선한다.
    • 에스컬레이션 절차를 분명하게 문서로 남긴다.
내 클러스터에도 있는지 확인하기

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

원인별1

운영 실수14건

  1. IL-0577 로컬 데이터베이스를 초기화하려던 prisma migrate reset 이 운영 Neon PostgreSQL 데이터베이스를 초기화했다
  2. IL-0286 Honeycomb 운영 Kafka 클러스터에서 브로커가 한꺼번에 사라져 토픽 파티션 여럿이 리더를 잃고 일부는 되살릴 수 없게 망가졌다
  3. IL-0576 테스트 코드가 운영 BigQuery 데이터 웨어하우스의 데이터셋을 전부 지웠다
  4. IL-0242 primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다
  5. IL-0565 쿠키런: 킹덤의 CockroachDB 클러스터가 노드 24대 중 16대의 파일 시스템을 잃고 멈춰 게임이 36시간 넘게 점검에 들어갔다
  6. IL-0332 운영 데이터베이스의 모든 테이블이 truncate 돼 travis-ci.com 이 약 5.5시간 동안 멈췄다
  7. IL-0431 AWS 로 옮기는 점검 중 PostgreSQL 이 의도하지 않게 장애 조치되어 한 시간 동안 갱신된 데이터가 사라졌다
    Hatena2017
  8. IL-0506 UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다
  9. IL-0218 복제를 다시 맞추다 PostgreSQL 프라이머리의 데이터 디렉터리가 지워지고 백업이 작동하지 않아 GitLab.com 이 약 18시간 멈추고 데이터를 잃었다
    GitLab2017
  10. IL-0573 개발자의 실수로 MariaDB 빌링 데이터베이스의 주요 테이블 9개가 DROP되어 배달의민족 결제가 잠시 멈췄다
  11. IL-0434 ElasticSearch 0.19 클러스터 전체 재시작 뒤 샤드 12개가 unassigned 로 남아 데이터 일부를 읽을 수 없었다
  12. IL-0319 mysql2 를 다시 적재하려고 데이터베이스를 지우는 동안 mysql1 의 데이터베이스도 함께 지워졌다
  13. IL-0538 사쿠라 클라우드 스토리지의 일괄 삭제 작업이 고객이 쓰던 디스크 53개를 지웠고 되살리지 못했다
  14. IL-0337 US East 리전 한 가용 영역에서 네트워크 변경으로 EBS 노드가 복제본과 끊겨 EBS 볼륨이 멈췄다