Incident Library

IL-0211 Mailchimp · 2019-02-04database

Mandrill 의 PostgreSQL 샤드 하나가 트랜잭션 ID 랩어라운드를 막으려고 쓰기를 멈춰 약 40시간 동안 메일 발송이 줄었다

Mandrill 은 키로 샤딩한 여러 PostgreSQL 데이터베이스를 하나의 키-값 저장소처럼 쓴다. PostgreSQL 은 쓰기마다 32비트 트랜잭션 ID 인 XID 를 매기고, 오래된 XID 를 autovacuum 으로 정리하지 않으면 약 20억 건 뒤에 번호가 한 바퀴 돌아 과거 트랜잭션이 미래처럼 보이게 된다. 2018년 11월 Mandrill 팀은 최대 부하 때 XID 가 한계의 절반쯤까지 오르는 것을 보고 랩어라운드 가능성을 알아챘지만, 당장 위협은 아니라고 보고 모니터링 추가를 백로그에 넣었다. 해시 알고리즘이 shard4 에 부하를 몰아 이 샤드의 autovacuum 이 밀렸거나 실패했고, 어느 쪽인지는 확인하지 못했다. 2019년 2월 4일 05:35 UTC 에 XID 가 상한에 닿아 shard4 가 안전 종료 모드로 들어갔다.

첫 예외는 다른 예외에 묻혀 늦게 발견됐고, 11:58 UTC 에 조사를 시작해 12:56 UTC 에 랩어라운드를 찾았다. 14:16 UTC 에 단일 사용자 모드에서 VACUUM 을 시작했지만 설정을 바꿔도 며칠에서 최대 40일이 걸린다는 추정이 나왔다. 팀은 VACUUM 과 함께 TB 단위인 Search 와 Url 테이블을 뺀 덤프와 복원을 시작했지만 이것도 매우 느렸다. 쓰기가 막히자 실패한 작업과 로그가 앱 서버 디스크를 채워 스토리지 볼륨을 갈아 끼웠다. 작업 대기열 테이블을 다른 샤드로 옮겼지만 효과는 작았다.

2월 5일 16:30 UTC 회의에서 Search 와 Url 테이블 없이도 앱이 돈다는 것을 확인했고, 잠긴 데이터베이스에서 두 테이블을 비우면 그 XID 가 풀린다는 것을 검증한 뒤 19:12 UTC 에 테이블을 비웠다. VACUUM 은 약 한 시간 뒤에 끝났다. 21:36 UTC 에 밀린 메일을 보내기 시작했고, 22:09 UTC 에 해결을 알렸으며, 22:40 UTC 에 XID 랩어라운드 알림을 새로 걸었다. 2월 6일 01:00 UTC 에 대기열이 비어 정상으로 돌아왔다. Mailchimp 는 2019년 1월 1일부터 2월 13일까지의 Mandrill 결제를 영향받은 사용자에게 환불했다.

심각도
–
영향 범위
Mandrill email sending: only 80% of queued emails were sent from 05:35 UTC on 4 February until 22:09 UTC on 5 February 2019
영향 사용자
Mandrill users; affected users were refunded for purchases made between 1 January and 13 February 2019
지역
–
감지까지
6h 23m
복구까지
40h 34m

증상

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

11:58 UTC 에 Mandrill 엔지니어가 조사를 시작했을 때 shard4 의 부하가 크게 올라 있었다. 실패한 쓰기를 다시 시도하는 요청이 shard4 로 몰린 탓이었고, 대응 팀은 처음에 치솟는 부하의 출처를 찾았다. 한 시간 뒤인 12:56 UTC 에 트랜잭션 ID 랩어라운드를 발견했다.

shard4 는 트랜잭션 ID 인 XID 가 상한에 닿아 안전 종료 모드로 들어가 쓰기를 받지 않았다. shard4 에 행을 써야 하는 작업이 실패해 Mandrill 앱 서버의 디스크에 대기열로 쌓였고, 이 작업들과 실패를 적은 로그 때문에 모든 앱 서버의 디스크 공간이 모자라기 시작했다. 잘못된 API 요청 로그와 실제 애플리케이션 오류를 구분하기 어려웠고, 작업 상태도 잘 보이지 않았다. 디스크 공간이 모자라 원인 분석을 돕는 부분 배포도 위험했다.

shard4 는 일부 작업 대기열을 추적하는 운영 테이블도 담고 있었다. 이 테이블을 살아 있는 다른 샤드로 옮겼지만, 대부분의 작업이 결국 shard4 에 다른 데이터를 써야 했으므로 별 도움이 되지 않았다.

단일 사용자 모드에서 시작한 VACUUM 은 며칠이 걸릴 것으로 보였다. 설정을 바꿔 다시 시작해도 며칠에서 몇 주가 걸린다는 추정이 나왔고, 가장 긴 추정은 40일이었다. VACUUM 이 도는 동안 TB 단위인 Search 와 Url 테이블을 뺀 덤프와 복원을 시작했지만 이것도 너무 느렸다. Mandrill 은 기본으로 48시간이 지난 대기 메일을 지우므로 밀린 메일에 남은 시간이 줄고 있었다.

증상 쓰기 거부처리량 저하부하 불균형노드 디스크 가득 참

원인

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

감지

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

조치

후속 조치

–

교훈

Mailchimp 는 대응을 늦춘 요인으로 다섯 가지를 들었다. Mandrill 시스템과 모니터링을 알고 접근할 수 있는 사람이 몇 명뿐이었다. Mandrill 의 로그 기록은 로컬에 쓸 수 있는 디스크에 기댔다. 잘못된 API 요청 로그와 실제 애플리케이션 오류를 구분하기 어려웠다. 작업 상태가 잘 보이지 않았다. 디스크 공간 문제로 원인 분석을 돕는 부분 배포가 위험했다.

잘된 점으로는 인시던트 지휘관을 중심으로 한 긴밀한 조율, 3시간 교대와 인수인계, 기록 담당과 기술 연락 담당 같은 역할 지정, 주기적인 요약과 결정 목록을 들었다. 화상 회의로 다시 모여 상황을 따져 본 것이 테이블을 비우는 해법으로 이어졌다. 대기 메일을 48시간 뒤에 지우는 기본 동작이 다가오자 더 과감한 방법을 검토하게 됐다. Mandrill 이 클라우드에서 돌아 스토리지를 빨리 늘릴 수 있었던 것은 운이었다. 지울 수 있는 데이터가 있었고 그 가운데 VACUUM 을 붙잡던 가장 큰 테이블이 있었던 것도 운이었다.

출처

관련 사례

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

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스, PostgreSQL vacuum. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.