IL-0391 GoCardless · 2015-05-27 (게시일)database
외래 키 제약을 다시 만드는 Postgres 마이그레이션이 잠금 대기열에 걸려 GoCardless API 가 15초쯤 멈췄다
GoCardless 는 1월 말 Pro 제품의 인보이스 기능을 만들고 있었다. 앞서 배포한 테이블의 이름을 바꿔야 했는데, 테이블은 비어 있었고 운영에서 그 테이블을 쓰는 코드도 없었다. 이름을 바꾼 뒤 외래 키 제약의 이름이 맞지 않아 제약을 지우고 다시 만들기로 했다. 테이블이 비어 있으니 제약을 검증하느라 잠금을 오래 잡을 일도 없다고 봤다.
배포하고 마이그레이션이 시작되자 곧 API 요청 타임아웃 알림이 왔고, 15초쯤 뒤 마이그레이션이 끝나면서 API 가 돌아왔다. 그날 이른 시각의 백업에 마이그레이션을 다시 돌리자 수백 밀리초 만에 끝났다. Postgres 는 외래 키 제약을 더할 때 제약을 강제하는 트리거를 만드는 동안 제약이 걸린 테이블과 참조되는 테이블 양쪽에 AccessExclusive 잠금을 잡는다. 다른 트랜잭션이 잡은 잠금 때문에 잠금을 얻지 못하면 대기열에 들어가고, 그 잠금과 충돌하는 잠금은 모두 그 뒤에 줄을 선다. AccessExclusive 는 다른 모든 잠금과 충돌하므로, 대기열에 하나만 있어도 그 테이블의 다른 작업이 모두 막힌다. NOT VALID 를 붙여 제약을 더해도 마찬가지다.
제약을 더한 테이블은 앱 코드가 쓰지 않았지만, 참조되는 테이블은 가장 많이 쓰는 테이블 가운데 하나였다. 마침 그 부모 테이블에서 오래 도는 읽기 쿼리가 돌고 있었고, 마이그레이션은 그 쿼리가 잡은 AccessShare 잠금 뒤에서 기다렸다. API 의 읽기·쓰기 쿼리가 그 뒤로 쌓여 클라이언트 쪽에서 타임아웃이 났다. 당시 GoCardless 는 Rails 마이그레이션에 그런 메서드가 없던 탓도 있어 Postgres 가 ALTER TABLE 로 제약 이름을 바꿀 수 있다는 것을 몰랐다. 이 방법은 참조되는 테이블의 AccessExclusive 잠금을 피하지만, 제약이 걸린 테이블에는 여전히 그 잠금을 잡는다.
- 심각도
- –
- 영향 범위
- The GoCardless API stopped answering requests for about 15 seconds
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- –
증상
계획된 데이터베이스 스키마 마이그레이션이 시작된 직후 API 요청에서 타임아웃이 난다는 알림이 왔다. API 는 15초쯤 요청을 받지 못하다가 마이그레이션이 끝나자 다시 살아났다.
마이그레이션의 ALTER TABLE 문 자체는 금방 끝나는 작업이었다. 하지만 이 문이 참조되는 부모 테이블의 AccessExclusive 잠금을 기다리는 동안 API 호출이 보낸 읽기와 쓰기 쿼리가 그 뒤에 쌓였고, 클라이언트 쪽에서 타임아웃이 났다.
처음에는 그날 이른 시각의 데이터베이스 백업에 같은 마이그레이션을 다시 돌려 봤다. 마이그레이션은 수백 밀리초 만에 끝나 문제가 재현되지 않았다. 인터넷에서 찾은 글 대부분은 무중단으로 스키마를 바꾸는 일반적인 방법만 다뤘고, 결국 Postgres 메일링 리스트의 오래된 스레드에서 같은 상황을 찾았다.
원인
원인 배포 결함
- 촉발
- Pro 제품의 인보이스 기능을 만들면서 이름을 바꾼 빈 테이블에서 외래 키 제약을 지우고 다시 만드는 마이그레이션을 배포했다. 그때 참조되는 테이블에서 오래 도는 읽기 쿼리가 실행 중이었다.
감지
스키마 마이그레이션이 시작된 직후 API 요청에서 타임아웃이 난다는 알림이 왔다.
조치
–
후속 조치
–
교훈
앱에서 오래 도는 쿼리와 트랜잭션을 없애고, 분석 쿼리는 비동기로 갱신되는 복제본에서 돌린다. log_min_duration_statement 와 log_lock_waits 를 설정하면 이런 문제가 장애로 번지기 전에 찾을 수 있다. 마이그레이션 스크립트에는 앱이 견딜 수 있는 만큼의 lock_timeout 을 둔다. 앱을 멈추는 것보다 배포를 중단하는 편이 낫다. 스키마 변경을 잘게 나누면 문제를 진단하기 쉽고, DDL 을 감싼 트랜잭션이 짧아져 잠금을 오래 잡지 않는다. Postgres 는 릴리스마다 잠금 코드가 나아지므로 최신으로 유지한다. 운영 데이터베이스에 쿼리를 날릴 수 있는 콘솔을 쓰는 개발자는 아주 조심해야 한다. 트랜잭션을 열어 둔 채 자리를 비우면 누군가 스키마 변경을 배포할 때 장애가 난다.
출처
관련 사례
같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
IL-0523TANF-app 의 운영 배포가 PostgreSQL 마이그레이션 실패로 두 달 동안 막힘U.S. Department of Health and Human Services · 2023같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
IL-0524인덱스를 다시 만드는 마이그레이션이 운영에서 Transactions 테이블을 잠가 Open Collective 서비스가 느려짐Open Collective · 2023같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
같은 원인 · 공통 데이터베이스
IL-0136Sweden Central 리전에서 내부 서비스 배포가 캐시를 비워 메타데이터 백엔드에 평소 20배의 트래픽이 몰렸고, 데이터베이스 연결에 기대는 헬스 체크가 인스턴스를 계속 재시작시켜 Azure OpenAI 요청이 6시간 가까이 실패했다Microsoft Azure · 2026같은 원인 · 공통 데이터베이스
IL-0510MIT Learn 운영 RDS PostgreSQL 의 저장 공간이 임시 파일로 가득 차 learn.mit.edu 가 세 시간 동안 503 과 504 를 냈다MIT Open Learning · 2026같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0547Buildkite 의 Agent API, REST API, GraphQL 이 annotations 테이블 쿼리 타임아웃으로 4시간 가까이 저하됐다Buildkite · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0324External Storage 데이터베이스 클러스터에 과부하가 걸려 mw-api-int, mw-parsoid, mw-jobrunner 의 지연과 데이터베이스 오류가 늘었다Wikimedia Foundation · 2025같은 원인 · 공통 데이터베이스
IL-0307Supabase us-east-1 에서 Supavisor 2.0 업그레이드 뒤 Postgres 연결 풀러가 테넌트 약 80% 의 연결을 끊었다Supabase · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0567쿠키런: 오븐브레이크의 패키지 매출 랭킹 기능을 배포한 뒤 Aurora MySQL CPU가 100%에 이르러 유저가 게임에 접속하지 못했다Devsisters · 2023같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0391
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0391/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.