IL-0523 U.S. Department of Health and Human Services · 2023-09-07database
TANF-app 의 운영 배포가 PostgreSQL 마이그레이션 실패로 두 달 동안 막힘
미국 보건복지부의 TANF-app 은 2023년 9월 7일부터 11월 9일까지 운영에 배포하지 못했다. 팀은 여러 사고가 하나로 겹쳤다고 밝혔다. 9월 7일 sprint 80 배포가 실패했고, 이때 이미 배포된 마이그레이션 search_indexes 0016 을 지우고 배포 환경의 자동 마이그레이션을 주석 처리했다. 9월 20일 sprint 82 배포는 자동 마이그레이션을 되살리고 0016 을 다시 만들었지만 디스크 공간 오류로 실패했고, 큰 마이그레이션 search indexes 0017 이 드러났다. 9월 7일과 9월 20일 사이에는 develop 파이프라인이 막혀 있었고, 9월 20일 뒤에는 staging 문제가 운영 배포를 막았다.
11월 1일 raft 환경에서 대량 데이터로 재현해 보았지만 마이그레이션이 성공했다. 데이터베이스 크기는 마이그레이션 중 최대 8배로 늘었다 돌아왔고 `temp_files` 는 약 4gb 늘었다. 11월 3일 팀은 큰 마이그레이션을 작게 나누기, PostgreSQL 의 `tmp_file_limit` 올리기, cloud.gov 의 데이터베이스 인스턴스 저장 공간 늘리기, 백업에서 새 인스턴스를 만들어 임시 테이블 공간 비우기를 해결 방안으로 꼽았다. 11월 6일 raft 환경의 저장 공간을 20gb 로 낮추자 임시 공간이 인스턴스 크기를 넘어 문제가 재현됐다.
11월 8일 운영 데이터베이스 인스턴스를 키우고 마이그레이션을 다시 돌리자, 빈 문자열을 정수로 바꾸는 오류가 새로 났다. 원인은 T3 레코드에 있었다. T3 레코드가 사례 파일 하나만 담고 둘째 사례 자리를 공백으로 채우지 않은 채 제출됐다. 파이썬의 부분 문자열 동작 때문에 이 값은 파싱 중 `value_is_empty` 검사를 빠져나갔고, 빈 문자열로 저장됐다. 모델의 필드 타입이 처음부터 맞았다면 마이그레이션이 실패하지 않았을 것이라고 팀은 밝혔다. 11월 9일 팀은 새 데이터베이스를 만드는 대신 운영 데이터베이스를 백업하고 `search_indexes` 테이블을 모두 비운 뒤 sprint 82 릴리스를 다시 배포했고, 운영 배포가 다시 가능해졌다. 빈 문자열 버그는 raft-tech/TANF-app PR 2748 로 고쳤다.
- 심각도
- –
- 영향 범위
- TANF-app production deployments were blocked from 7 September to 9 November 2023
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- 63d
증상
2023년 9월 7일 TANF-app 의 sprint 80 운영 배포가 실패했고, 9월 20일 sprint 82 배포는 디스크 공간 오류로 실패했다. 그 뒤 11월 9일까지 운영에 배포하지 못했다.
9월 7일 배포에서 팀은 이미 배포된 마이그레이션 search_indexes 0016 을 지웠고, 배포 환경에서 `makemigrations` 와 `migrate` 를 주석 처리해 마이그레이션을 손으로 돌리게 했다. 9월 20일 배포는 이 주석을 풀고 망가진 0016 을 다시 만들었는데, 이때 디스크 공간 오류가 났고 큰 마이그레이션인 search indexes 0017 이 드러났다. 두 배포 사이에는 develop 파이프라인이 여러 이유로 막혀 있었고, 9월 20일 뒤에는 staging 문제가 운영 배포를 막았다.
11월 1일 팀은 raft 환경에 t1, t2, t3, t4, t5 를 각각 70만~100만 행 올리고, 로컬 환경에서 배포된 데이터베이스를 대상으로 마이그레이션을 돌려 문제를 재현해 보았다. 마이그레이션 중 데이터베이스 크기는 최대 8배까지 늘었다가 끝나면 처음 크기로 돌아왔고, `temp_files` 는 끝난 뒤 약 4gb 늘어 있었다. 레코드 수가 운영보다 훨씬 많은데도 마이그레이션은 성공했고, 팀은 데이터베이스 설정 차이 때문이라고 보았다.
11월 6일 raft 환경의 데이터베이스 인스턴스 저장 공간을 20gb 로 낮추자, 마이그레이션을 여러 번 돌린 끝에 임시 공간이 인스턴스 크기를 넘어 문제가 재현됐다.
11월 8일 운영 데이터베이스 인스턴스를 키우고 마이그레이션을 다시 돌리자, 빈 문자열을 정수로 바꾸다 실패하는 새 오류가 났다. 해당 마이그레이션에 의심 가는 곳이 많아 손으로는 빈 필드를 찾지 못했고, "Debugging commands" 의 "check for blank strings in CharFields" 함수로 T3 의 필드들을 짚어 냈다. T3 의 모든 문자 필드에 `None` 이 아니라 `''` 가 든 레코드가 있었다.
원인
- 촉발
- 2023년 9월 7일 sprint 80 배포에서 이미 배포된 마이그레이션 search_indexes 0016 을 지웠고, 9월 20일 sprint 82 배포가 이 마이그레이션을 다시 만들어 큰 마이그레이션 0017 과 함께 운영 데이터베이스에서 돌렸다.
감지
운영 배포가 실패하면서 드러났다. 9월 7일 sprint 80 배포와 9월 20일 sprint 82 배포가 차례로 실패했고, 9월 20일 배포에서 디스크 공간 오류가 났다.
조치
- 11월 8일 운영 데이터베이스 인스턴스 크기를 키우고 마이그레이션을 다시 돌렸다. 이번에는 빈 문자열을 정수로 바꾸는 오류로 마이그레이션이 실패했다.
- 운영 데이터베이스의 백업을 만들었다.
- `search_indexes` 테이블을 모두 비웠다. 팀은 여기서 운영 데이터를 잃는 것을 걱정하지 않았다.
- sprint 82 릴리스를 다시 배포해 운영을 sprint 82 로 되살렸고, 배포가 다시 가능해졌다.
- 빈 문자열 버그를 고쳤다.
후속 조치
- 데이터베이스 문제 해결 절차를 기술 문서에 더한다.
- 위키 페이지와 운영 플레이북에 흔한 문제와 대응을 정리한다.
- Failed-Deployment-Troubleshooting 문서에 데이터베이스 문제 절을 만들고 cf revisions 명령을 적는다.
- 운영에 직접 한 변경을 모은 GitHub 레포를 만들어, 비슷한 문제가 다시 날 때 길잡이로 쓴다.
- 운영 디버깅 세션의 터미널 출력을 저장한다.
- 코드 리뷰에서 지킬 Django 마이그레이션 모범 사례를 만든다.
- develop 에 머지된 마이그레이션은 고치지 않고, 그 시점부터 모델을 바꾸는 새 마이그레이션을 쓴다.
- 자동 생성된 마이그레이션 이름을 알아보기 쉬운 이름으로 바꾼다. 이름만 보고 내용을 알 수 없으면 조사하기 어렵다.
- 자동 생성 마이그레이션은 모델 변경 하나에 하나만 두고, PR 하나에서 모델을 여러 번 바꾸면 마이그레이션을 다시 생성한다.
- 시험에 쓸 데이터베이스 시딩과 백업을 어떻게 할지 논의 자리를 만든다.
교훈
계획에 없던 일이라 많은 개발자가 손을 보태야 했고, 팀이 작아 분야마다 모든 사람이 필요했다. 팀은 문제와 대응을 일찍 문서로 남겨 더 적은 사람으로 대응할 수 있게 하자고 했다. 작업 중인 코드를 raft, qasp, sandbox 환경에 배포한 뒤 마이그레이션을 바꾸면 그 환경에서 충돌이 난다. 대용량 데이터와 공백 채움이 없는 레코드 같은 예외 상황은 시험하지 않았고, 개인정보를 지운 운영 데이터베이스 사본이 있으면 실제 데이터로 더 많이 시험할 수 있다.
출처
참고 문서
관련 사례
같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
같은 원인 · 공통 데이터베이스, 스키마 마이그레이션
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-0523
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0523/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.