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

사례14건

14건 중 7~11번째

  1. 증상

    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' 는 평소 스탠바이 서버에 두어도 문제가 없으므로, 사람이 빠뜨릴 여지를 없애려고 슬레이브를 만들 때 기본으로 넣도록 운영을 바꿨다. 이 설정을 쓰려는 사람은 자기 구성에서 문제가 없는지 먼저 확인하라고 덧붙였다.

  2. 증상

    새벽 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 를 쓰기 시작한다.
    • 모니터링과 호출 체계를 개선한다.
    • 에스컬레이션 절차를 분명하게 문서로 남긴다.
  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. 증상

    OpenStack 호스트 서버가 다운되어 ElasticSearch 노드 하나가 내려간 뒤, API 서버 쪽 ElasticSearch 헬스 체크가 false 를 반환했다. Head 플러그인으로 본 클러스터 상태는 그린이었다.

    촉발

    OpenStack 호스트 서버가 다운되어 30대짜리 ElasticSearch 클러스터의 노드 하나가 내려갔다.

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

    API 서버가 ElasticSearch 에 하는 헬스 체크가 false 를 반환했다. 클러스터 상태는 Head 플러그인에서 그린으로 보였다.

    조치
    • 호스트가 언제 다시 다운될지 몰라, 복구된 ElasticSearch 노드 서버를 다른 호스트 서버로 마이그레이션했다.
    • 마이그레이션 뒤 ElasticSearch 프로세스를 띄워 노드를 클러스터에 다시 넣었다.
    • 헬스 체크를 되살리려고 1번 노드부터 로그를 보며 차례로 전체 재시작했지만, 스무 번째 노드까지 재시작했을 즈음 API 서버 지연이 생겨 멈췄다.
    • 다시 1번 노드부터 차례로 전체 재시작해 API 서버 지연과 헬스 체크를 되살렸다.
    • _cluster/reroute 의 allocate 명령으로 unassigned 샤드를 옮기려 했지만 오류가 나서 실패했다.
    • A 인덱스의 53번 샤드는 22번 노드에만 파일이 남아 있었다. 그래서 프로세스를 멈추고 그 파일을 디렉터리만 남은 3번 노드와 10번 노드로 복사한 뒤, 22번 노드의 상태 파일만 "primary" 를 true 로 바꿨다.
    • B 인덱스의 51번 샤드는 lucene-core 검사로 27번 노드 쪽이 망가진 것을 확인했다. 27번 노드의 파일을 지운 뒤 28번 노드의 파일을 27번 노드와 다른 노드로 복사하고, 28번 노드만 "primary" 를 true 로 바꿨다.
    • C 인덱스의 2번 샤드는 27번 노드에만, 4번 샤드는 25번 노드에만 파일이 남아 있었다. 그래서 각 파일을 다른 두 노드로 복사하고, 복사한 쪽의 상태 파일은 "primary" 를 false 로 바꿨다.
    후속 조치
    • 오래된 ElasticSearch 버전을 그만 쓰고 버전을 올린다.
    교훈

    재시작할 때는 자동 재배치를 끄고 작업해야 했다. 노드 하나를 재시작한 뒤에는 로그만 볼 것이 아니라 재배치가 완전히 끝나는 것을 보고 나서 다음 노드를 재시작해야 했다.

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

이 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 볼륨이 멈췄다