{"id":"IL-0487","org":"Autumn","date":"2026-03-15","docs":[],"title":{"en":"Autumn's Postgres CPU spiked and about 10% of API requests failed after a collation change left the main query unindexed","ko":"Postgres 테이블 두 개의 콜레이션을 바꾼 뒤 데이터베이스 CPU가 치솟아 Autumn API 요청의 약 10%가 실패했다"},"domain":["database"],"impact":{"scope":"Autumn API: about 10% of requests failed from 15:00 to 16:15 GMT","users":"unknown","region":"both regions"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":null,"related":[],"sources":[{"url":"https://useautumn.com/blog/post-mortem-database-outage-caused-by-collation-migration-locking-conflict","kind":"blog","archived":"https://web.archive.org/web/20260415090634/https://useautumn.com/blog/post-mortem-database-outage-caused-by-collation-migration-locking-conflict"}],"summary":{"en":"Autumn was optimising its most frequent queries and found that several indexes were not used because of a collation mismatch: customers.internal_id uses COLLATE \"C\" for KSUID ordering, while foreign key columns such as customer_entitlements.internal_customer_id used the default collation, and Postgres cannot use indexes across that mismatch. The most frequent query took about 150 ms instead of under 10 ms. Changing the collation on customer_entitlements first cut CPU by 30%, so the team decided to align four tables in the order customer_entitlements, customer_products, entities, customers. After the first two tables were changed, the main query lost the indexes it needed, database CPU spiked and queries failed.\n\nRolling back with ALTER TABLE deadlocked repeatedly against active queries, and a code-level cast did not help quickly enough. At 15:48, with help from PlanetScale support, the team shut down all application connections, reverted the collation changes, ran ANALYZE to rebuild the planner statistics and restarted. Queries returned to normal at once and CPU levelled off, and full recovery in both regions was confirmed at 16:15.","ko":"Autumn은 가장 자주 도는 질의를 최적화하다가 부모 테이블과 자식 테이블의 콜레이션이 달라 인덱스가 쓰이지 않는 것을 찾았다. customers.internal_id는 KSUID 정렬을 위해 COLLATE \"C\"를 쓰는데, customer_entitlements.internal_customer_id 같은 외래 키 열은 기본 콜레이션을 썼다. Postgres는 콜레이션이 다른 열 사이에서 인덱스를 쓰지 못해, 10ms 안에 끝나야 할 이 질의가 약 150ms 걸렸다. customer_entitlements의 콜레이션을 바꾸자 CPU가 30% 줄고 질의가 훨씬 빨라졌다. 팀은 customer_entitlements, customer_products, entities, customers 네 테이블의 콜레이션을 이 순서로 맞추기로 했다.\n\n2026-03-15 15:00 GMT 무렵 앞의 두 테이블을 바꾸자 주 질의가 인덱스를 잃어 성능이 크게 떨어졌고, 데이터베이스 CPU가 치솟으며 질의가 실패했다. ALTER TABLE로 되돌리려 했지만 배타 잠금이 필요했고, 잠금을 잡은 활성 질의가 늘 있어 교착 상태가 되풀이됐다. 질의 SQL에서 콜레이션을 맞추는 코드 수정도 부하 속에서 빨리 효과를 내지 못했다. 15:48에 PlanetScale 지원팀의 도움을 받아 모든 애플리케이션 연결을 끊고, 콜레이션 변경을 되돌리고, ANALYZE로 플래너 통계를 다시 만든 뒤 재시작했다. 질의는 바로 정상으로 돌아왔고, 16:15에 두 리전 모두에서 API와 대시보드가 정상임을 확인했다."},"trigger":{"en":"A migration that aligned column collations across related tables changed the collation of customer_entitlements and then customer_products on the production database.","ko":"가장 자주 도는 질의를 최적화하던 중 관련 테이블 네 개의 콜레이션을 맞추기로 하고, 운영 데이터베이스에서 customer_entitlements와 customer_products의 콜레이션을 바꿨다."},"severity":"unknown","symptoms":["database-overloaded","high-error-rate","lock-contention"],"timeline":{"detected_after":"5m","resolved_after":"1h 15m"},"date_kind":"incident","detection":null,"components":["database"],"root_cause":"config-change","remediation":[{"text":{"en":"Shut down all application connections to the database","ko":"15:48에 PlanetScale 지원팀의 도움을 받아 모든 애플리케이션 연결을 끊었다."}},{"text":{"en":"Reverted the collation changes","ko":"콜레이션 변경을 되돌렸다."}},{"text":{"en":"Ran ANALYZE to rebuild the planner statistics and restarted","ko":"ANALYZE로 플래너 통계를 다시 만든 뒤 재시작했다. 질의가 바로 정상으로 돌아왔고 CPU도 안정됐다."}}],"action_items":[{"text":{"en":"Do all future column and schema migrations with the duplicate-dual-write-cutover pattern: add a new column, dual-write, backfill in small batches, create indexes concurrently, then cut over","ko":"앞으로 열과 스키마 마이그레이션은 모두 복제, 이중 쓰기, 전환 순서로 한다. 열을 제자리에서 바꾸지 않고 원하는 타입의 새 열을 만들어 두 열에 함께 쓰고, 기존 행을 작은 묶음으로 채우고, 인덱스를 동시 생성한 뒤 전환한다."}},{"text":{"en":"Add a status page","ko":"문제가 생겼을 때 상황을 투명하게 알리려고 상태 페이지를 만든다."}},{"text":{"en":"Consider making the check and track SDKs fail open by default","ko":"장애가 나도 유료 고객이 영향을 받지 않도록 check와 track SDK가 기본으로 fail-open 하게 바꾸는 방안을 검토한다."}}],"contributing":["revert-did-not-restore"],"recurrence_of":null,"symptoms_text":{"en":"On Sunday March 15, 2026 at about 15:00 GMT, right after collation changes were applied to the customer_entitlements and customer_products tables, database CPU on Autumn's production Postgres spiked and queries began to fail. About 10% of API requests to Autumn failed until 16:15 GMT.\n\nThe team identified the cause at 15:05 and tried to roll the collation back with ALTER TABLE. ALTER TABLE needs an exclusive lock on the table, and active queries always held locks on it, so the rollback ran into repeated deadlocks. At 15:20 a code change that cast the collation explicitly in the main customer query was pushed to get the indexes used again without a schema rollback, but with the database under heavy load it did not take effect fast enough.","ko":"2026-03-15 15:00 GMT 무렵 운영 데이터베이스의 두 테이블에 콜레이션 변경을 적용하자 데이터베이스 CPU가 치솟고 질의가 실패하기 시작했다. 16:15 GMT까지 Autumn API 요청의 약 10%가 실패했다.\n\n고객 정보를 읽는 주 질의가 인덱스를 쓰지 못하게 되면서 성능이 크게 떨어졌다. 이 질의가 데이터베이스 CPU를 끌어올렸다.\n\n15:05에 문제를 찾고 ALTER TABLE로 변경을 되돌리려 했다. ALTER TABLE은 테이블에 배타 잠금이 필요했는데 잠금을 잡은 활성 질의가 늘 있어서 교착 상태가 되풀이됐다.\n\n15:20에는 스키마를 되돌리지 않고 인덱스를 쓰게 하려고 고객 정보를 읽는 주 질의에서 콜레이션을 명시적으로 캐스트하는 코드 수정을 배포했다. 데이터베이스 부하가 높아 이 수정은 충분히 빨리 효과를 내지 못했다."},"updated_at":"2026-11-14T12:34:13.270540Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0487","org_slug":"autumn"}