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

사례7건

7건 중 4~7번째

  1. 증상

    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 스케줄러가 멈춘 것을 몰랐다. 도구와 절차 때문에 읽기 전용 팔로워에 접속하기가 어려웠고, 그래서 운영 데이터를 살펴볼 때 쓰기 권한이 있는 주 데이터베이스에 바로 접속하는 지름길이 흔해졌다. 가장 경험 많은 개발자도 실수로 큰 장애를 낼 수 있다는 점을 다시 확인했다. 대응하면서 놓친 단계가 있었고, 그 단계를 밟았다면 문제가 줄었을 것이다.

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

    2013년 7월 18일 2:42 AM PDT 에 모니터링 시스템이 결제용 Redis 클러스터의 장애를 당번 엔지니어에게 알렸다. redis-master 호스트의 부하가 극도로 높았고, redis-master 에 기대는 서비스들이 실패하고 있었다.

    촉발

    7월 18일 1:35 AM PDT 에 네트워크 연결이 끊겨 결제용 redis-slave 전부가 동시에 redis-master 와 끊겼고, 다시 붙으면서 한꺼번에 전체 동기화를 요청했다. 부하가 높아진 redis-master 를 당번 엔지니어가 재시작했다.

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

    2:42 AM PDT 에 모니터링 시스템이 Redis 클러스터 장애를 당번 엔지니어링 팀에 알렸다. 잘못된 결제는 3:28 AM PDT 에 결제 시스템 모니터링이 이상 동작을 보고해 알았고, 12:36 PM PDT 에는 모니터링이 같은 이상의 재발을 찾았다.

    조치
    • 2:42 AM PDT 에 당번 엔지니어가 높은 부하를 해결하려고 redis-master 를 재시작했다.
    • 4:10 AM PDT 에 신용카드 결제가 더 일어나지 않게 결제 시스템을 내렸다.
    • 6:24 AM PDT 에 정지된 계정을 모두 되살렸고, 결제 시스템은 원인을 조사하는 동안 꺼 둔 채로 뒀다.
    • 별도 관계형 데이터 저장소에 따로 적어 둔 잔액 기록으로 redis-master 에서 잃은 계정 잔액을 복원했다.
    • 12:36 PM PDT 에 같은 이상이 다시 나타나자 결제 시스템을 다시 내리고 정지된 계정을 바로 되살렸다.
    • 2:57 PM PDT 부터 잘못된 신용카드 결제를 환불해 7월 19일 3:00 PM PDT 에 모두 마쳤다.
    • 7월 19일 3:30 PM PDT 에 영향받은 모든 계정에 최근 30일 Twilio 사용액의 10% 를 크레딧으로 줬다.
    • 장애를 일으킨 원래 Redis 클러스터를 교체했고, redis-master 의 잘못된 설정을 찾아 고쳤다.
    • 7월 19일 7:10 PM PDT 부터 고객 계정 묶음마다 결제 시스템을 차례로 켰다.
    후속 조치
    • redis-master 의 Redis 재시작을 막고, 앞으로 redis-master 복구는 slave 를 승격해 처리한다.
    • 결제 잔액이 없거나 쓸 수 없으면 계정을 정지하거나 신용카드를 결제하지 않도록 안전장치를 넣는다.
    • 결제 시스템이 잔액을 따로 적어 두는 데이터베이스와 실시간으로 대조하게 바꾼다.
    교훈

    Twilio 는 현재 잔액 데이터와 잔액을 갱신하는 능력을 동시에 잃자 자동 충전 시스템이 위험한 방향으로 실패했다고 봤다. 잔액이 없거나 쓸 수 없을 때 자동 충전 시스템은 계정을 정지하지 않고 카드를 결제하지 않는 쪽으로 멈춰야 했다.

  4. 증상

    2013년 1월 24일 목요일 예외 추적·모니터링 시스템이 예외가 크게 치솟은 것을 잡았다. 예외 대부분은 코드 검색 질의의 타임아웃과, 새 push 의 데이터로 코드 검색 색인을 갱신하는 백그라운드 작업의 타임아웃이었다.

    촉발

    2013년 1월 17일 코드 검색 클러스터를 elasticsearch 0.19.9 에서 0.20.2 로 업그레이드했다. 그 뒤 코드 검색을 공개하자 클러스터에 높은 부하가 걸렸고 메모리 할당이 계속 일어났다.

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

    예외 추적·모니터링 시스템이 예외가 크게 치솟은 것을 잡았다.

    조치
    • 클러스터를 완전히 멈추고, 샤드 할당과 재균형을 끄는 "maintenance mode" 로 다시 올렸다.
    • elasticsearch 를 개발하고 지원하는 회사의 엔지니어 두 명에게 도움을 청했다.
    • 두 엔지니어의 권고대로 Java 와 elasticsearch 를 곧바로 업그레이드하고 설정을 바꿨다. Puppetmaster 에 이 변경 전용 토픽 브랜치와 환경을 만들고 각 노드에서 Puppet 을 돌렸다.
    후속 조치
    • 코드 검색 클러스터의 스테이징 환경을 만들어 인프라 변경을 더 잘 시험한다.
    • elasticsearch 팀이 앞으로도 코드 검색 클러스터 설정을 다시 점검해, 쓰임새에 맞게 유지되도록 한다.
    교훈

    GitHub 는 elasticsearch 0.20.2 를 코드 검색 클러스터에 내보내기 전에 자기 인프라에서 충분히 시험하지 않았고 다른 클러스터에서도 시험하지 않았다. 코드 검색 클러스터에 제대로 된 스테이징 환경이 없던 것이 한 요인이었다. elasticsearch 팀은 큰 클러스터에 이렇게 권했다. ES_HEAP_SIZE 환경 변수로 JVM 의 최소·최대 메모리를 같게 하고 시스템 RAM 의 50% 로 둔다. recover_after_time 을 30분으로 두지 말고 복구가 바로 시작되게 한다. minimum_master_nodes 를 설정해 노드가 오래 멈출 때 노드 일부가 따로 클러스터를 이루지 않게 한다.

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

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

원인별4

운영 실수4건

  1. IL-0286 Honeycomb 운영 Kafka 클러스터에서 브로커가 한꺼번에 사라져 토픽 파티션 여럿이 리더를 잃고 일부는 되살릴 수 없게 망가졌다
  2. IL-0242 primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다
  3. IL-0332 운영 데이터베이스의 모든 테이블이 truncate 돼 travis-ci.com 이 약 5.5시간 동안 멈췄다
  4. IL-0506 UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다

배포 결함1건

조직
  1. IL-0591 Gitpod 워크스페이스가 새 클러스터 세대 배포 뒤 열흘 넘게 모든 리전에서 느려지고 시작 실패와 데이터 유실이 났다
    Gitpod2022

설정 변경1건

조직
  1. IL-0215 Twilio 결제 시스템의 Redis 가 잔액 데이터를 잃고 읽기 전용이 되어 자동 충전 고객의 신용카드가 반복 결제됐다
    Twilio2013

소프트웨어 결함1건

조직
  1. IL-0274 GitHub 코드 검색의 elasticsearch 클러스터에서 일부 노드 CPU 가 100% 에 가깝게 차고 마스터가 계속 바뀌면서 검색과 색인이 타임아웃되고 샤드 데이터가 사라졌다
    GitHub2013