{"id":"IL-0486","org":"Figma","date":"2020-01-22","docs":[],"title":{"en":"Figma's PostgreSQL 9 database on RDS slowed under high write IOPS and lock contention until the site was unavailable","ko":"Figma의 PostgreSQL 데이터베이스에서 쓰기 IOPS와 락 경합이 치솟아 서비스가 하루 동안 여러 번 내려갔다"},"domain":["database","aws"],"impact":{"scope":"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","users":"some users","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Having upgraded staging to PostgreSQL 11, tested it for months and written a production upgrade plan let the team upgrade under poor conditions with confidence. A routine statistics refresh can flip a query to a catastrophic plan, and the same query's plan can differ widely over time, so comparing plans from snapshots taken at different dates exposed the cause.","ko":"스테이징 데이터베이스를 미리 PostgreSQL 11로 올려 몇 달 시험하고 운영 업그레이드 계획을 써 둔 덕분에 좋지 않은 상황에서도 업그레이드를 결정할 수 있었다. autovacuum을 막은 뒤에도 성능 문제가 남았으므로 autovacuum은 기여 요인이었을 뿐 유일한 원인이 아니었다. 같은 질의의 행 추정이 시점마다 크게 달랐다는 것은 통계 문제의 신호였다."},"related":[],"sources":[{"url":"https://www.figma.com/blog/post-mortem-service-disruption-on-january-21-22-2020/","kind":"blog","archived":"https://web.archive.org/web/20260730125504/https://www.figma.com/blog/post-mortem-service-disruption-on-january-21-22-2020/"}],"summary":{"en":"Figma runs PostgreSQL on Amazon RDS. On January 21 a long-running, expensive query raised database CPU until it was cancelled. On January 22 a routine automatic ANALYZE changed the statistics of a table used by a complex ORM-generated query. ANALYZE samples rows, and it had increasingly underestimated the selectivity of the innermost subquery; once the estimate for an inner HashAggregate crossed 200 rows, a deficiency in the PostgreSQL 9 query planner made the total estimate jump from 199 rows to over 20 million. The resulting plan used full table scans and wrote large amounts of data to temporary buffers, consistent with the high write IOPS and temp bytes metrics seen during the incident. At the same time, the backlog left by the query cancelled on January 21 had started aggressive anti-wraparound autovacuum, which added to the locking and writes.\n\nThe team cancelled inessential queries, raised IOPS, cancelled autovacuum statements, and at 3:00 PM restarted the database to apply a change to autovacuum_freeze_max_age that stopped aggressive autovacuum; that reboot caused part of the downtime between 2 PM and 3 PM. Because the database stayed unhealthy, the team took the service down at 7:00 PM for emergency maintenance and upgraded to PostgreSQL 11, which had been tested in staging for several months with a written upgrade plan. The site was back at 8:15 PM and all site and database metrics returned to normal.\n\nAfterwards the team restored a snapshot from before the upgrade, reduced the query to nested sub-selects on the roles table, reproduced the bad estimate by running ANALYZE manually, and bisected PostgreSQL versions with the official Docker images: the test case from a PostgreSQL mailing list thread reproduced in 9.6.16 but not in 10.0. The 10.0 change \"Use uniqueness guarantees to optimize certain join types\" lets the planner use the primary key's uniqueness, and on the upgraded database EXPLAIN VERBOSE shows \"Inner Unique: true\" for the query. PostgreSQL 11 also handles autovacuum much better than version 9.","ko":"2020-01-21 오전 6시 11분 PST에 Figma 엔지니어링 팀은 오류율 상승 알림을 받았다. 장시간 도는 비싼 질의 하나가 데이터베이스 CPU를 올리고 있었고, 6시 54분에 이 질의를 취소하자 성능이 정상으로 돌아왔다. 이 질의 때문에 vacuum이 밀렸고, 트랜잭션 ID wraparound를 막는 공격적인 autovacuum이 돌 기준을 넘었다.\n\n다음 날 오전 9시 45분 PST부터 쓰기 IOPS와 락 경합이 늘었고, 사이트는 11시 42분과 오후 2시에 일부 사용자에게 멈췄다. 팀은 질의를 취소하고 IOPS를 늘리고 autovacuum 문을 취소했으며, autovacuum_freeze_max_age를 바꾸고 데이터베이스를 재부팅했다. 그래도 지표가 평소보다 높아, 스테이징에서 몇 달 시험하고 운영 계획까지 써 둔 PostgreSQL 11로 올리기로 했다. 오후 7시에 서비스를 내리고 Amazon RDS의 PostgreSQL을 업그레이드했고, 8시 15분에 사이트와 데이터베이스 지표가 정상으로 돌아왔다.\n\n업그레이드 전 스냅숏으로 분석해 보니, 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 성능도 크게 나아졌다."},"trigger":{"en":"An automatic ANALYZE on one of the tables in the query finished at exactly the moment the incident began. The new statistics pushed the planner's row estimate for an inner subquery past 200 rows, and the plan for the whole query switched to an estimate of over 20 million rows.","ko":"사고가 시작된 바로 그 시각에 테이블 하나의 자동 ANALYZE가 끝나 통계가 바뀌었고, PostgreSQL 9 쿼리 플래너가 ORM이 만든 질의 하나의 행 수를 크게 잘못 추정했다."},"severity":"unknown","symptoms":["lock-contention","database-overloaded","service-unavailable"],"timeline":{"detected_after":"15m","resolved_after":"10h 30m"},"date_kind":"incident","detection":{"en":"Database metrics showed higher write IOPS and lock contention at 9:45 AM PST, and an automated alert at 10:00 AM started the investigation.","ko":"2020-01-22 오전 10시 PST에 자동 알림을 받고 조사를 시작했다. 데이터베이스 지표의 변화는 9시 45분부터 보였다. 전날 사고도 오전 6시 11분 PST의 오류율 상승 자동 알림으로 알아챘다."},"components":["database","autovacuum"],"root_cause":"software-bug","remediation":[{"text":{"en":"Cancelled several inessential queries and increased the allocated IOPS","ko":"데이터베이스 부하를 줄이려고 꼭 필요하지 않은 질의 몇 개를 취소하고 할당 IOPS를 늘렸다."}},{"text":{"en":"Cancelled autovacuum statements running on the largest tables","ko":"가장 큰 테이블들에서 돌던 autovacuum 문을 취소했다. 지표가 잠시 나아졌지만 autovacuum이 다시 시작됐다."}},{"text":{"en":"Changed autovacuum_freeze_max_age and restarted the database to stop aggressive autovacuuming","ko":"공격적인 autovacuum을 완전히 막으려고 autovacuum_freeze_max_age 파라미터를 바꾸고 데이터베이스를 재부팅했다."}},{"text":{"en":"Took the service down for emergency maintenance and upgraded the database to PostgreSQL 11","ko":"오후 7시 PST에 서비스를 내려 긴급 유지보수를 하고, 스테이징에서 미리 시험한 PostgreSQL 11로 데이터베이스를 올렸다. 오후 8시 15분 PST에 사이트를 다시 열었다."}}],"action_items":[{"text":{"en":"Revisit transaction timeouts and limits so long-running and expensive queries are terminated","ko":"장시간 도는 비싼 질의가 끊기도록 트랜잭션 타임아웃과 한도를 다시 정한다."}},{"text":{"en":"Improve metrics, monitoring and database settings for autovacuuming, locking and query performance","ko":"autovacuum, 락, 질의 성능과 관련된 지표, 모니터링, 데이터베이스 설정을 개선한다."}},{"text":{"en":"Audit queries for unnecessary or excessive joins and subqueries that could trip up the query planner","ko":"쿼리 플래너를 헷갈리게 할 수 있는 불필요하거나 지나친 조인과 하위 질의가 있는지 질의를 점검한다."}},{"text":{"en":"Bring the performance tools of the upgraded PostgreSQL into the incident response plans","ko":"업그레이드한 PostgreSQL에서 쓸 수 있는 새 성능 분석 도구를 장애 대응 계획에 넣어 문제 질의를 더 빨리 가려낸다."}},{"text":{"en":"Audit the structure and access patterns of data stored in PostgreSQL and consider moving some of it to datastores suited to those patterns","ko":"PostgreSQL에 들어 있는 데이터 종류별 구조와 접근 방식을 점검하고, 일부 데이터는 접근 방식에 맞는 저장소로 옮기는 것을 검토한다."}},{"text":{"en":"Improve in-app messaging about unexpected maintenance windows","ko":"예정에 없던 유지보수 시간을 앱 안에서 알리는 방식을 개선한다."}},{"text":{"en":"Move towards online database upgrades that do not need downtime","ko":"앞으로 중단 시간이 필요 없는 온라인 데이터베이스 업그레이드로 옮겨 간다."}},{"text":{"en":"Let users keep working on already open files safely while offline, even after closing the browser","ko":"오프라인이 된 뒤 다시 연결되기 전에 브라우저를 닫아도, 이미 열어 둔 파일에서 계속 안전하게 작업할 수 있게 하는 일을 시작한다."}}],"contributing":["overlapping-failures","manual-action-worsened"],"recurrence_of":null,"symptoms_text":{"en":"On January 22, 2020 at 9:45 AM PST, Figma's database metrics showed rising write IOPS and lock contention, while database CPU was lower than normal. An automated alert followed at 10:00 AM, and by 11:42 AM enough API requests had queued up behind the database that Figma became unavailable for some users.\n\nThe pressure showed in write IOPS, write latency, lock contention and the temp bytes metric. One statement with complex subqueries, generated by Figma's ORM layer, kept appearing in the lock contention. Its plan estimated over 20 million rows when the real result was 3 rows, did full table scans instead of using an index and wrote large amounts of data to temporary buffers, and its execution time stretched into minutes.\n\nThe disruption came in waves. Cancelling several inessential queries and raising the allocated IOPS stabilised performance for a while, but at 2:00 PM the database degraded again and the site went down a second time. A restart at 3:00 PM brought relative stability, but write IOPS, write latency and lock contention stayed higher than normal until the evening upgrade.\n\nThe first theory was aggressive autovacuum. A long-running query cancelled the previous day had left a vacuuming backlog that crossed the threshold for the autovacuum mode that prevents transaction ID wraparound, which weighs more on locking and writes in the PostgreSQL version Figma ran. Cancelling autovacuum statements on the largest tables helped only briefly, because autovacuum resumed, and even after it was disabled the performance problems continued.","ko":"2020-01-22 오전 9시 45분 PST부터 데이터베이스 지표에서 쓰기 IOPS와 락 경합이 늘었다. 전날 사고와 달리 데이터베이스 CPU는 평소보다 낮았다. 오전 10시에 자동 알림이 왔고, 일부 사용자는 Figma를 불러오지 못했다.\n\n데이터베이스 부하 때문에 API 요청이 큐에 쌓였다. 쓰기 IOPS, 쓰기 지연, 락 경합, temp bytes 지표가 높았고, 복잡한 하위 질의가 들어간 SQL 문 하나가 락 경합에 얽힌 질의로 거듭 나타났다.\n\n그 SQL 문의 실행 계획은 결과를 2천만 행 넘게 추정했지만 실제 결과는 3행이었다. 인덱스를 써야 할 자리에서 전체 테이블 스캔을 했고, 임시 버퍼에 많은 데이터를 썼다. 실행 시간은 몇 분으로 늘어났다.\n\n오전 11시 42분 PST에 일부 사용자에게 서비스가 멈췄다. 꼭 필요하지 않은 질의를 취소하고 IOPS를 늘리자 잠시 안정됐지만, 오후 2시에 성능이 다시 나빠져 사이트가 또 멈췄다. 오후 3시에 데이터베이스를 재시작한 뒤 안정됐지만 쓰기 IOPS, 쓰기 지연, 락 경합은 여전히 평소보다 높았다.\n\n처음에는 autovacuum을 가장 유력한 원인으로 봤다. 전날 취소한 장시간 질의 때문에 vacuum이 밀려, 트랜잭션 ID wraparound를 막는 공격적인 autovacuum이 돌고 있었다. 가장 큰 테이블들의 autovacuum 문을 취소하자 지표가 잠시 나아졌지만 autovacuum이 다시 시작되면서 쓰기 IOPS와 락 경합도 돌아왔다. autovacuum을 막은 뒤에도 성능 문제가 남아 autovacuum만의 문제가 아니라고 결론지었다."},"updated_at":"2026-11-14T12:34:13.169622Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0486","org_slug":"figma"}