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

사례5건

5건 중 1~5번째

  1. 증상

    월말 마지막 영업일 아침 6시쯤부터 Cloud Pub/Sub 메시지가 밀리기 시작했고, Cloud Spanner에 새 회계 데이터를 등록하지 못하는 상태가 간헐적으로 이어졌다. 밀린 메시지는 1000만 건 규모에 이르렀다. 복구와 재발을 거듭하며 수습하기까지 하루가 넘게 걸렸다.

    촉발

    장애 한 달여 전 Cloud Functions를 1st gen에서 Cloud Run 기반 2nd gen으로 옮기면서 --max-instances 를 지정하지 않아, 스케일 상한이 1st gen 기본값인 사실상 무제한에서 2nd gen 기본값인 100으로 내려갔다. 월말 아침 부하가 쉬는 구간 없이 약 40분 이어지자 이 상한에 걸렸다.

    원인
    구성 요소
    기여 요인
    조치
    • 별도 pull 구독으로 Dataflow가 Cloud Storage에 저장해 둔 데이터를 다시 넣어 데이터 손실을 피했다.
    후속 조치
    • max-instances 를 비롯한 스케일 관련 파라미터를 배포 정의에 모두 명시해 세대나 기본값이 바뀌어도 영향을 받지 않게 했다. max-instances 는 Spanner 부하를 생각해 100에서 1000까지 단계적으로 올렸다.
    • 콜드 스타트를 피하려고 min-instances 를 1로 두고, 핸들러의 공유 상태가 안전한지 확인한 뒤 concurrency 를 1에서 10으로 올렸다. 이에 맞춰 cpu는 1, memory는 512MB에서 1Gi로 늘렸다.
    • mercari/spanner-autoscaler를 관리하는 SRE 팀이 High priority CPU와 Total CPU를 OR 조건으로 보는 dual CPU scaling mode를 구현했다. 팀은 이 기능이 담긴 v0.8.0을 도입해 Total CPU 임계값 70%를 새로 뒀다.
    • 스케일업이 더 빨리 시작되도록 High priority CPU 임계값을 60%에서 55%로 내렸다.
    • 월말 피크가 시작될 때 확보한 PU가 줄지 않도록 아침 5시부터 7시까지 Spanner 스케일다운을 막는다.
    • 월말 아침이나 대규모 리컨사일이 도는 달처럼 부하를 예상할 수 있는 기간에는 일정에 따라 PU를 미리 확보한다.
    • Datadog에 Cloud Run 인스턴스 수가 상한의 40%와 60%에 이르면 각각 경고 단계와 심각 단계로 알리는 모니터를 더했다.
    • Datadog에 Pub/Sub의 전달되지 않은 메시지 수가 임계값을 넘으면 알리는 모니터를 더했다. 오탐을 피하려고 보수적인 임계값에서 시작해 평소 상태를 알아 가며 단계적으로 낮춘다.
    교훈

    관리형 서비스의 세대를 옮길 때는 직접 바꾸지 않은 기본값이 부하가 걸릴 때 문제로 드러난다. 스케일 상한, 동시 처리 수, 타임아웃처럼 평소에는 효과가 보이지 않는 파라미터는 옮길 때마다 명시해야 한다. 장애 원인은 하나가 아닐 수 있으므로 촉발, 증폭, 실제 피해로 이어진 요인을 각각 봐야 한다. 설정 최적화와 그 한계에 가까워지는 것을 알아채는 모니터링은 늘 함께 갖춰야 하고, 물려받아 운영하는 시스템일수록 설정과 전제를 정기적으로 다시 살펴야 한다.

  2. 증상

    13:30 UTC 에 ZooKeeperWait 지표가 갑자기 오른다는 알림이 왔다. 이 클러스터의 알림 기준은 낮은 쪽 10초, 높은 쪽 20초인데 값은 수천 초였다. 값은 대칭으로 이중화된 인스턴스 세 곳에서 똑같이 올랐다.

    촉발

    몇 달 동안 네트워크에서 떨어져 있던 클라이언트 머신 두 대가 다시 연결되면서, 그동안 쌓인 몇 달 치 헬스 체크 데이터를 아주 작은 배치로 빠르게 보냈다.

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

    13:30 UTC 에 팀은 ZooKeeperWait 지표가 갑자기 오른다는 알림을 받았다. 이 클러스터 크기에 맞춘 알림 기준은 낮은 쪽 10초, 높은 쪽 20초였다.

    조치
    • 쌓인 파트를 끝내 정리하지 못한 5분 단위 파티션 테이블을 장애 뒤 DROP-CREATE 로 다시 만들어 정상으로 되돌렸다.
    • 그 테이블의 파티션 단위를 분에서 시간으로 넓혔다.
    후속 조치
    • 루트 MaterializedView 테이블에서 범위를 벗어난 타임스탬프를 거부하도록 구현했다.
    • 두 번째 MaterializedView 가 거부된 데이터의 통계를 기록한다.
    • 거부 범위는 과거 데이터 백필을 위해 일부러 넓게 잡았고, 거부되는 데이터를 모아 가며 범위를 다듬는다.
    • 타임스탬프가 며칠이나 몇 달에 걸친 insert 쿼리 하나로 이번 상황을 재현하는 최소 시험 시나리오를 만들었다.
    • 관측 기능을 여러 가지 개선한다.
    교훈

    이 파이프라인은 주로 데이터 양을 다루지만, 어긋난 타임스탬프도 카디널리티 변화만큼 파이프라인을 흔들 수 있었다. 이상한 지표를 바로 설명하지 못했다고 치워 두면 안 된다. 이번에도 원인으로 이끈 것은 처음에 무시한 insert 크기 감소였다. 중앙값과 평균은 각자 쓸모가 있고, 이번에는 평균이 실마리였다. 파티션이 너무 적으면 파트가 커지고 mutation 이 느려지며 TTL 이 너무 비싸지거나 아예 동작하지 않는다. 반대로 너무 많으면 열 스캔과 집계 쿼리가 느려지고 Keeper 가 과부하된다. Keeper 작업은 파트 수에 따라 선형보다 훨씬 빠르게 늘었다.

  3. 증상

    2025년 11월 11일 16:02 UTC 부터 PostHog 수집 처리 파이프라인의 성능이 크게 떨어져, US 리전 모든 고객의 이벤트 처리가 지연되기 시작했다. 고객이 최근에 보낸 이벤트가 PostHog 에 보이지 않았다.

    촉발

    Persons 테이블의 properties 열은 자주 2KB 를 넘어 TOAST 테이블에 따로 저장되는데, 이 값마다 할당하는 32비트 OID 공간이 거의 다 찼다. 그러자 쓰기 한 건마다 비어 있는 OID 를 찾느라 이미 쓴 OID 를 길게 훑어야 했다.

    원인
    구성 요소
    기여 요인
    조치
    • 새 TOAST 테이블과 새 OID 공간을 갖는 파티션 Persons 테이블 구조를 새로 만들었다.
    • 실시간 쓰기가 옛 테이블과 새 테이블에 함께 맞춰지도록 데이터베이스 트리거를 넣었다.
    • 백필 스크립트로 기존 Person 데이터를 옛 테이블에서 새 테이블로 복사했다.
    • 새 쓰기는 모두 새 테이블로 가고, 옮기는 동안 읽기는 새 테이블을 먼저 본 뒤 옛 테이블로 넘어가도록 수집과 애플리케이션 로직을 바꿨다.
    • 장애 중 새 문제를 만들 위험이 있어, 웹 앱은 완전 자동 롤아웃 대신 사람이 통제하는 배포로 운영에 내보냈다. 엔지니어 여럿이 주말 내내 교대로 Feature flags API, Error tracking 수집, Django 웹 애플리케이션을 고쳤다.
    • 새 Persons 테이블에서 수집 성능이 돌아온 뒤, 파티션별 지연, 처리량, 자원 사용률을 지켜보며 수집 워커를 늘려 밀린 이벤트를 처리했다.
    • MSK 로컬 디스크 사용률이 약 85% 에 닿자 수집을 잠시 멈췄다.
    • 해당 Kafka 토픽의 로컬 보존 기간을 줄여, S3 로 넘기기 전에 디스크에 두는 데이터를 줄였다.
    • 디스크 여유가 충분한 것을 확인한 뒤 수집을 다시 시작했다.
    • 오래 걸리는 백필 작업을 더 잘 지켜보고 안정적으로 돌리려고 백필 과정을 Dagster 로 옮겼고, 남은 백필과 정리 작업을 주말 동안 Dagster 로 끝냈다.
    후속 조치
    • 쓰기마다 디스크 읽기가 크게 늘어나는 현상에 지표와 알림을 더한다.
    • TOAST 테이블 통계에 지표와 알림을 더한다.
    • 큰 규모에서 문제가 될 수 있는 다른 Postgres 엔진 한계에도 지표와 알림을 더한다.
    • 비슷한 Postgres 엔진 수준 문제의 증상과 진단 방법을 런북으로 정리하고, 옮길지 제자리에서 고칠지 정하는 의사 결정 트리를 분명하게 넣는다.
    • 고객에게 언제 어떻게 알릴지, 누구에게 에스컬레이션할지, 누가 대신 맡을지를 분명히 정한 고객 소통 런북을 새로 만든다.
    • persons 데이터베이스에 다른 데이터 저장소를 쓰는 방안을 계속 검토한다.
    • 새 테이블로 통합하는 작업을 마무리한다.
    • 백필 도구와 Kafka tiered storage 의 안전장치를 개선한다.
    • Person 과 비슷한 테이블을 처음부터 비슷한 한계에 닿지 않게 설계하고 운영한다.
    교훈

    Persons 테이블 하나에 analytics, feature flag, replay 필터, CDP 같은 핵심 기능 다수가 기대고 있어, 이 테이블이 느려지자 제품의 넓은 부분이 영향을 받았다. TOAST 증가, OID 사용량, 쓰기당 디스크 읽기 증폭을 보는 모니터링이 없어 OID 고갈을 원인으로 확정하는 데 하루 반쯤 걸렸다. 처음의 백필 방식은 길고 큰 마이그레이션에 필요한 가시성과 견고함이 모자라 장애 중에 Dagster 로 옮겨야 했다. MSK 디스크가 차오른 일로 tiered storage 설정과 알림이 큰 백로그 상황에 맞춰져 있지 않았다는 것이 드러났다. 평소 고객 소통을 맡는 팀원이 장애 내내 모두 자리에 없어 대신할 사람을 급히 찾아야 했다. PostHog 는 잘된 점으로 네 가지를 꼽았다. 데이터를 잃지 않았다. 여러 팀과 외부 컨설턴트, AWS 엔지니어가 함께 드문 엔진 수준 문제를 진단했다. 시스템을 살려 둔 채 트리거와 백필로 새 Persons 테이블로 옮겼다. 엔지니어가 직접 정기적으로 상태를 알렸다.

  4. 증상

    Aurora MySQL 의 reader 가 이유를 알 수 없이 재시작했다. 그 직후부터 writer 의 insert latency 가 7배쯤 오르는 등 writer 성능이 크게 떨어진 상태가 오래 이어졌다. 같은 일이 2019년 11월 두 주 사이에 두 번 일어났다.

    촉발

    reader 에서 오래 도는 쿼리 때문에 롤백 세그먼트가 계속 커진 끝에 reader 가 재시작했다. 글쓴이는 그 쿼리가 Redash 에서 reader 로 보낸 것이었다고 기억한다.

    원인
    구성 요소
    기여 요인
    조치
    • 팀은 writer 성능이 원래대로 돌아올 때까지 지켜보기만 했다.
    후속 조치
    • 롤백 세그먼트 길이를 Datadog 의 mysql.innodb.history_list_length 로 감시하는 모니터를 더했다.
    • reader 가 재시작할 만큼 오래 도는 쿼리를 찾으면 바로 kill 하게 했다.
    • 모니터 알림이 거의 매주 울리게 되자 사내에 알린 뒤 max_execution_time 을 설정해, 일정 시간이 지난 쿼리를 강제로 끝내게 했다.
    교훈

    롤백 세그먼트가 커지지 않도록 트랜잭션을 작게 해야 한다. 롤백 세그먼트는 클러스터 전체가 함께 쓰므로, 한번 커지면 인스턴스를 바꿔도 해제가 끝나기를 기다리는 수밖에 없다. 롤백 세그먼트 길이는 show engine innodb status 의 "History list length" 값으로 볼 수 있다.

  5. 증상

    2019년 2월 4일 05:35 UTC 부터 Mandrill 이 대기열에 쌓인 메일의 80% 만 보냈다. 첫 예외는 일요일 밤에 로그에 남았지만 다른 시스템의 관계없는 예외에 묻혀 중앙 오류 보고에서 눈에 띄지 않았다.

    촉발

    Mandrill 의 키 해시 알고리즘이 shard4 에 쓰기를 더 많이 몰아 이 샤드의 autovacuum 이 밀렸거나 실패했고, 2019년 2월 4일 05:35 UTC 에 XID 가 상한에 닿아 데이터베이스가 안전 종료 모드로 들어갔다.

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

    첫 예외는 일요일 밤에 로그에 남았지만 다른 시스템의 관계없는 예외에 묻혀 중앙 오류 보고에서 늦게 발견됐다. 11:58 UTC 에 Mandrill 엔지니어가 조사를 시작했고, shard4 의 부하 급증을 쫓다가 12:56 UTC 에 랩어라운드를 찾았다.

    조치
    • 14:16 UTC 에 shard4 를 단일 사용자 모드로 띄우고 VACUUM 을 시작했다.
    • VACUUM 이 며칠 걸릴 것으로 보여 설정을 바꿔 속도를 높인 뒤 다시 시작했다.
    • VACUUM 이 도는 동안 데이터베이스 덤프와 복원을 시작했고, 속도를 높이려고 TB 단위인 Search 와 Url 테이블을 뺐다.
    • 실패한 작업과 로그로 디스크가 모자라자 Mandrill 앱 서버들의 스토리지 볼륨을 교체했다.
    • shard4 에 있던 작업 대기열 테이블을 살아 있는 다른 샤드로 옮겨 작업이 처리되게 하려 했다.
    • 잠긴 데이터베이스에서 테이블을 비우면 그 XID 가 풀린다는 것을 확인한 뒤 2월 5일 19:12 UTC 에 Search 와 Url 테이블을 비웠고, VACUUM 이 약 한 시간 뒤에 끝났다.
    • 21:36 UTC 에 밀린 메일 발송을 시작했다.
    • 22:40 UTC 에 XID 랩어라운드 알림을 새로 걸었다.
    교훈

    Mailchimp 는 대응을 늦춘 요인으로 다섯 가지를 들었다. Mandrill 시스템과 모니터링을 알고 접근할 수 있는 사람이 몇 명뿐이었다. Mandrill 의 로그 기록은 로컬에 쓸 수 있는 디스크에 기댔다. 잘못된 API 요청 로그와 실제 애플리케이션 오류를 구분하기 어려웠다. 작업 상태가 잘 보이지 않았다. 디스크 공간 문제로 원인 분석을 돕는 부분 배포가 위험했다. 잘된 점으로는 인시던트 지휘관을 중심으로 한 긴밀한 조율, 3시간 교대와 인수인계, 기록 담당과 기술 연락 담당 같은 역할 지정, 주기적인 요약과 결정 목록을 들었다. 화상 회의로 다시 모여 상황을 따져 본 것이 테이블을 비우는 해법으로 이어졌다. 대기 메일을 48시간 뒤에 지우는 기본 동작이 다가오자 더 과감한 방법을 검토하게 됐다. Mandrill 이 클라우드에서 돌아 스토리지를 빨리 늘릴 수 있었던 것은 운이었다. 지울 수 있는 데이터가 있었고 그 가운데 VACUUM 을 붙잡던 가장 큰 테이블이 있었던 것도 운이었다.

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

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

원인별2

업그레이드로 깨진 호환성1건

조직
  1. IL-0572 Merpay 회계 이벤트 처리 시스템에서 Cloud Pub/Sub 메시지 1000만 건이 밀려 새 회계 데이터가 하루 넘게 Cloud Spanner에 들어가지 못했다

용량 한계4건

  1. IL-0288 CDN77 의 ClickHouse 데이터 파이프라인에서 이중화된 인스턴스 세 곳의 ZooKeeperWait 가 수천 초로 치솟아 복제가 한 시간 넘게 느려졌다
    CDN772026
  2. IL-0390 PostHog US Cloud 의 Persons Postgres 데이터베이스 쓰기가 크게 느려져 이벤트와 persons 수집이 최대 2일 지연됐다
  3. IL-0443 Aurora MySQL reader 가 이유 없이 재시작한 뒤 writer 의 insert latency 가 7배쯤 오르고 replication lag 가 하루 넘게 이어졌다
    Repro2019
  4. IL-0211 Mandrill 의 PostgreSQL 샤드 하나가 트랜잭션 ID 랩어라운드를 막으려고 쓰기를 멈춰 약 40시간 동안 메일 발송이 줄었다