Incident Library

IL-0486 Figma · 2020-01-22databaseaws

Figma의 PostgreSQL 데이터베이스에서 쓰기 IOPS와 락 경합이 치솟아 서비스가 하루 동안 여러 번 내려갔다

2020-01-21 오전 6시 11분 PST에 Figma 엔지니어링 팀은 오류율 상승 알림을 받았다. 장시간 도는 비싼 질의 하나가 데이터베이스 CPU를 올리고 있었고, 6시 54분에 이 질의를 취소하자 성능이 정상으로 돌아왔다. 이 질의 때문에 vacuum이 밀렸고, 트랜잭션 ID wraparound를 막는 공격적인 autovacuum이 돌 기준을 넘었다.

다음 날 오전 9시 45분 PST부터 쓰기 IOPS와 락 경합이 늘었고, 사이트는 11시 42분과 오후 2시에 일부 사용자에게 멈췄다. 팀은 질의를 취소하고 IOPS를 늘리고 autovacuum 문을 취소했으며, autovacuum_freeze_max_age를 바꾸고 데이터베이스를 재부팅했다. 그래도 지표가 평소보다 높아, 스테이징에서 몇 달 시험하고 운영 계획까지 써 둔 PostgreSQL 11로 올리기로 했다. 오후 7시에 서비스를 내리고 Amazon RDS의 PostgreSQL을 업그레이드했고, 8시 15분에 사이트와 데이터베이스 지표가 정상으로 돌아왔다.

업그레이드 전 스냅숏으로 분석해 보니, ORM이 만든 복잡한 질의를 줄여 roles 테이블 하나를 중첩된 하위 질의로 읽는 질의만 남겨도 같은 문제가 났다. 사고 며칠 전 스냅숏에서는 이 질의의 행 추정이 199였는데, 사고 시작 시각에 끝난 자동 ANALYZE 뒤에는 2천만 행을 넘었다. 원인은 안쪽 HashAggregate의 추정 행 수가 200을 넘으면 전체 추정이 크게 뛰는 플래너의 약점이었다. 원래 질의에서는 이 때문에 전체 테이블 스캔과 임시 버퍼 쓰기가 일어났다. ANALYZE는 행 표본을 쓰므로 같은 값이 몰려 있는 열의 고유 값 수를 적게 추정할 수 있다. 공식 PostgreSQL Docker 이미지로 버전을 이분 탐색하자 9.6.16에서는 재현됐고 10.0에서는 재현되지 않았다. 팀은 10.0 릴리스 노트의 "Use uniqueness guarantees to optimize certain join types" 변경이 기본 키 조인인 이 질의에 해당한다고 봤고, 업그레이드한 데이터베이스에서 EXPLAIN VERBOSE 출력에 "Inner Unique: true"가 있는 것을 확인했다. 공격적인 autovacuum은 이 문제를 키웠고, PostgreSQL 11에서는 autovacuum 성능도 크게 나아졌다.

심각도
–
영향 범위
Figma web service: intermittent issues and failures to load Figma at several points during January 22, plus an emergency maintenance downtime from 7:00 PM to 8:15 PM PST
영향 사용자
some users
지역
–
감지까지
15m
복구까지
10h 30m

증상

2020-01-22 오전 9시 45분 PST부터 데이터베이스 지표에서 쓰기 IOPS와 락 경합이 늘었다. 전날 사고와 달리 데이터베이스 CPU는 평소보다 낮았다. 오전 10시에 자동 알림이 왔고, 일부 사용자는 Figma를 불러오지 못했다.

데이터베이스 부하 때문에 API 요청이 큐에 쌓였다. 쓰기 IOPS, 쓰기 지연, 락 경합, temp bytes 지표가 높았고, 복잡한 하위 질의가 들어간 SQL 문 하나가 락 경합에 얽힌 질의로 거듭 나타났다.

그 SQL 문의 실행 계획은 결과를 2천만 행 넘게 추정했지만 실제 결과는 3행이었다. 인덱스를 써야 할 자리에서 전체 테이블 스캔을 했고, 임시 버퍼에 많은 데이터를 썼다. 실행 시간은 몇 분으로 늘어났다.

오전 11시 42분 PST에 일부 사용자에게 서비스가 멈췄다. 꼭 필요하지 않은 질의를 취소하고 IOPS를 늘리자 잠시 안정됐지만, 오후 2시에 성능이 다시 나빠져 사이트가 또 멈췄다. 오후 3시에 데이터베이스를 재시작한 뒤 안정됐지만 쓰기 IOPS, 쓰기 지연, 락 경합은 여전히 평소보다 높았다.

처음에는 autovacuum을 가장 유력한 원인으로 봤다. 전날 취소한 장시간 질의 때문에 vacuum이 밀려, 트랜잭션 ID wraparound를 막는 공격적인 autovacuum이 돌고 있었다. 가장 큰 테이블들의 autovacuum 문을 취소하자 지표가 잠시 나아졌지만 autovacuum이 다시 시작되면서 쓰기 IOPS와 락 경합도 돌아왔다. autovacuum을 막은 뒤에도 성능 문제가 남아 autovacuum만의 문제가 아니라고 결론지었다.

증상 잠금 경합과 교착데이터베이스 과부하서비스 중단

원인

촉발
사고가 시작된 바로 그 시각에 테이블 하나의 자동 ANALYZE가 끝나 통계가 바뀌었고, PostgreSQL 9 쿼리 플래너가 ORM이 만든 질의 하나의 행 수를 크게 잘못 추정했다.

감지

2020-01-22 오전 10시 PST에 자동 알림을 받고 조사를 시작했다. 데이터베이스 지표의 변화는 9시 45분부터 보였다. 전날 사고도 오전 6시 11분 PST의 오류율 상승 자동 알림으로 알아챘다.

조치

후속 조치

교훈

스테이징 데이터베이스를 미리 PostgreSQL 11로 올려 몇 달 시험하고 운영 업그레이드 계획을 써 둔 덕분에 좋지 않은 상황에서도 업그레이드를 결정할 수 있었다. autovacuum을 막은 뒤에도 성능 문제가 남았으므로 autovacuum은 기여 요인이었을 뿐 유일한 원인이 아니었다. 같은 질의의 행 추정이 시점마다 크게 달랐다는 것은 통계 문제의 신호였다.

출처

관련 사례

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

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

AI 도구와 스크립트용

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