IL-0572 Mercari · 2026-06-16 (게시일)gcpdatabase
Merpay 회계 이벤트 처리 시스템에서 Cloud Pub/Sub 메시지 1000만 건이 밀려 새 회계 데이터가 하루 넘게 Cloud Spanner에 들어가지 못했다
Merpay 회계 시스템은 사내 마이크로서비스가 보내는 회계 이벤트를 Cloud Pub/Sub에서 push 구독으로 Cloud Run에 넘기고 Cloud Spanner에 기록한다. 기록된 데이터는 보낸 서비스와 대조하는 리컨사일을 거쳐 가맹점 정산과 매달 하는 회계 마감에 쓰인다. 모든 처리가 비동기라 Cloud Run과 Spanner의 처리 능력이 파이프라인 전체의 처리량 상한이 되고, 월말과 월초에는 평소의 수십 배 부하가 몰린다. Pub/Sub 메시지는 별도의 pull 구독으로 Dataflow가 읽어 Cloud Storage에도 저장한다.
장애 한 달여 전 Cloud Functions를 2nd gen으로 옮길 때 --max-instances 를 명시하지 않아 상한이 100으로 내려갔지만, 평소에는 100 인스턴스로 충분해 드러나지 않았다. 이전 월말도 maxInstances 100, eventMaxAge 21분, 3시간 트래픽 총량 약 17~19GB로 조건이 거의 같았지만, 부하 사이사이에 쉬는 구간이 있어 Spanner CPU 부하와 ack되지 않은 메시지의 적체가 그때마다 풀렸다. 장애 당일에는 쉬는 구간 없이 피크가 약 40분 이어졌다. 상한에 걸린 Cloud Run은 메시지를 다 처리하지 못했다. 인스턴스가 한꺼번에 쓰는 탓에 Spanner CPU가 치솟아 처리가 더 느려졌고, eventMaxAge 를 넘긴 메시지는 버려졌다. Spanner autoscaler는 High priority CPU만 봐서 Total CPU가 100%인데도 스케일업하지 않았다.
데이터 손실은 Cloud Storage의 데이터를 다시 넣어 피했다. 그러나 Spanner에 등록되지 않은 데이터는 리컨사일이 끝나지 않은 채 남았고, 마감일과 겹쳐 업무 전반에 영향을 줬다. 팀은 Cloud Run 스케일 파라미터를 배포 정의에 모두 명시하고 값을 올렸으며, mercari/spanner-autoscaler에 Total CPU 기준 스케일링을 넣고, 상한 접근과 메시지 적체를 알리는 모니터링을 더했다.
- 심각도
- –
- 영향 범위
- Merpay accounting event pipeline: new accounting data was not registered in Cloud Spanner and stayed unreconciled, so it could not go on to merchant settlement and monthly closing; falling on a closing day, this caused wide business impact
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- –
증상
월말 마지막 영업일 아침 6시쯤부터 Cloud Pub/Sub 메시지가 밀리기 시작했고, Cloud Spanner에 새 회계 데이터를 등록하지 못하는 상태가 간헐적으로 이어졌다. 밀린 메시지는 1000만 건 규모에 이르렀다. 복구와 재발을 거듭하며 수습하기까지 하루가 넘게 걸렸다.
Cloud Run 인스턴스 수는 maxInstances=100 에 걸려 더 늘지 않았고, 상한까지 찬 인스턴스가 한꺼번에 쓰면서 Spanner CPU가 치솟았다. 한 건을 쓰는 시간이 길어지자 처리량이 더 떨어지고 밀린 메시지가 더 쌓였다. 밀린 시간이 eventMaxAge 를 넘은 메시지는 Spanner에 등록되지 못한 채 버려졌다.
Spanner의 Total CPU는 100%에서 내려오지 않았지만, autoscaler는 Spanner의 계산 용량 단위인 PU, 곧 Processing Unit을 전혀 늘리지 않았다. autoscaler가 보는 High priority CPU는 임계값 60%를 계속 넘지 않아 스케일업 조건을 채우지 못했다.
Cloud Run 인스턴스가 스케일 상한에 닿았을 때 알려 주는 알림이 없었다. 팀에게는 어느 월말에 갑자기 장애가 난 것처럼 보였지만, 나중에 보니 상한에 가까워진 달도 이미 있었다.
원인
- 촉발
- 장애 한 달여 전 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의 전달되지 않은 메시지 수가 임계값을 넘으면 알리는 모니터를 더했다. 오탐을 피하려고 보수적인 임계값에서 시작해 평소 상태를 알아 가며 단계적으로 낮춘다.
교훈
관리형 서비스의 세대를 옮길 때는 직접 바꾸지 않은 기본값이 부하가 걸릴 때 문제로 드러난다. 스케일 상한, 동시 처리 수, 타임아웃처럼 평소에는 효과가 보이지 않는 파라미터는 옮길 때마다 명시해야 한다. 장애 원인은 하나가 아닐 수 있으므로 촉발, 증폭, 실제 피해로 이어진 요인을 각각 봐야 한다. 설정 최적화와 그 한계에 가까워지는 것을 알아채는 모니터링은 늘 함께 갖춰야 하고, 물려받아 운영하는 시스템일수록 설정과 전제를 정기적으로 다시 살펴야 한다.
출처
관련 사례
같은 원인 · 공통 데이터베이스, 메시지 브로커
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0316밀린 배포가 한꺼번에 몰린 Control Plane 에서 새 버전 PgBouncer 가 CPU 와 메모리를 비정상적으로 많이 써 데이터베이스 연결이 타임아웃됐다Railway · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0444Aurora MySQL 에서 ALTER TABLE 을 실행할 때마다 그 테이블을 reader 로 읽던 커넥션이 끊겨 애플리케이션 오류가 쏟아졌다Repro · 2024같은 원인 · 공통 데이터베이스
IL-0117인그레스 업그레이드가 네트워크 컨트롤 플레인 회귀를 일으켜 데이터 플레인이 잠깐 끊겼고, 한꺼번에 재시작한 서비스들이 백엔드 저장소에 과부하를 일으켜 OpenAI API 오류가 늘었다OpenAI · 2024같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0308RevenueCat 이 Aurora PostgreSQL 10 에서 14 새 클러스터로 넘어간 직후 쿼리가 극도로 느려져 대시보드와 SDK 엔드포인트가 세 시간 넘게 실패했다RevenueCat · 2022같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0572
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0572/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.