{"id":"IL-0523","org":"U.S. Department of Health and Human Services","date":"2023-09-07","docs":["https://github.com/HHS/TANF-app/pull/476","https://github.com/raft-tech/TANF-app/releases/tag/v3.1.5-Sprint-82","https://github.com/raft-tech/TANF-app/pull/2748","https://github.com/raft-tech/TANF-app/blob/develop/docs/Technical-Documentation/Failed-Deployment-Troubleshooting.md"],"title":{"en":"TANF-app production deployments were blocked for two months when a search-index migration ran out of database disk space","ko":"TANF-app 의 운영 배포가 PostgreSQL 마이그레이션 실패로 두 달 동안 막힘"},"domain":["database"],"impact":{"scope":"TANF-app production deployments were blocked from 7 September to 9 November 2023","users":"unknown","region":"unknown"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The work was unplanned and pulled in many developers from a small team, so the team wants documentation and playbooks that let fewer people step away from current work. Large datasets and unexpected edge cases such as rows without space-fill had not been tested, and a copy of the production database with PII scrubbed would allow tests against real-world data. Deploying in-progress code to other environments and changing migrations afterwards had caused conflicts there.","ko":"계획에 없던 일이라 많은 개발자가 손을 보태야 했고, 팀이 작아 분야마다 모든 사람이 필요했다. 팀은 문제와 대응을 일찍 문서로 남겨 더 적은 사람으로 대응할 수 있게 하자고 했다. 작업 중인 코드를 raft, qasp, sandbox 환경에 배포한 뒤 마이그레이션을 바꾸면 그 환경에서 충돌이 난다. 대용량 데이터와 공백 채움이 없는 레코드 같은 예외 상황은 시험하지 않았고, 개인정보를 지운 운영 데이터베이스 사본이 있으면 실제 데이터로 더 많이 시험할 수 있다."},"related":[],"sources":[{"url":"https://github.com/HHS/TANF-app/blob/main/docs/Technical-Documentation/postmortems/2023.11.09-large-migrations-outage.md","kind":"github","archived":null}],"summary":{"en":"TANF-app is a Django application whose production database is a PostgreSQL instance on cloud.gov. The team counts the incident, from 7 September to 9 November 2023, as several incidents combined. A previously deployed search_indexes migration, 0016, had been deleted and later recreated, which caused the first deployment failures and made fixing deployments more complex. When the sprint 82 release ran on 20 September, the large migration search_indexes 0017 grew the database's temporary space beyond the instance storage and failed with a disk space error, and production stayed blocked for two months while staging problems and blocked pipelines added delay.\n\nIn November the team reproduced the disk failure in its raft environment by lowering the instance storage to 20gb, and listed ways out: split large migrations into smaller ones, raise the postgres tmp_file_limit, increase the instance storage in cloud.gov, or wipe the temp table space by creating a new instance from a backup. On 8 November the production instance size was increased and the migrations were re-run, which exposed a second problem: the migration failed converting a blank string to an integer. T3 rows submitted with a single case and no space-filling for the second case had passed through the value_is_empty check during parsing because of a Python substring quirk and were stored as blank strings. Correct field types in the models would have kept the migrations from failing.\n\nOn 9 November, to avoid creating a new database, the team backed up the production database, truncated all of the search_indexes tables, whose data it was not worried about losing, and redeployed the sprint 82 release. Production was restored to sprint 82 and deployments could move forward. The blank-string parsing bug was fixed in a separate pull request.","ko":"미국 보건복지부의 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 문제가 운영 배포를 막았다.\n\n11월 1일 raft 환경에서 대량 데이터로 재현해 보았지만 마이그레이션이 성공했다. 데이터베이스 크기는 마이그레이션 중 최대 8배로 늘었다 돌아왔고 `temp_files` 는 약 4gb 늘었다. 11월 3일 팀은 큰 마이그레이션을 작게 나누기, PostgreSQL 의 `tmp_file_limit` 올리기, cloud.gov 의 데이터베이스 인스턴스 저장 공간 늘리기, 백업에서 새 인스턴스를 만들어 임시 테이블 공간 비우기를 해결 방안으로 꼽았다. 11월 6일 raft 환경의 저장 공간을 20gb 로 낮추자 임시 공간이 인스턴스 크기를 넘어 문제가 재현됐다.\n\n11월 8일 운영 데이터베이스 인스턴스를 키우고 마이그레이션을 다시 돌리자, 빈 문자열을 정수로 바꾸는 오류가 새로 났다. 원인은 T3 레코드에 있었다. T3 레코드가 사례 파일 하나만 담고 둘째 사례 자리를 공백으로 채우지 않은 채 제출됐다. 파이썬의 부분 문자열 동작 때문에 이 값은 파싱 중 `value_is_empty` 검사를 빠져나갔고, 빈 문자열로 저장됐다. 모델의 필드 타입이 처음부터 맞았다면 마이그레이션이 실패하지 않았을 것이라고 팀은 밝혔다. 11월 9일 팀은 새 데이터베이스를 만드는 대신 운영 데이터베이스를 백업하고 `search_indexes` 테이블을 모두 비운 뒤 sprint 82 릴리스를 다시 배포했고, 운영 배포가 다시 가능해졌다. 빈 문자열 버그는 raft-tech/TANF-app PR 2748 로 고쳤다."},"trigger":{"en":"The sprint 82 deployment on 20 September 2023 recreated the deleted search_indexes migration 0016 and ran the large migration search_indexes 0017 against the production database.","ko":"2023년 9월 7일 sprint 80 배포에서 이미 배포된 마이그레이션 search_indexes 0016 을 지웠고, 9월 20일 sprint 82 배포가 이 마이그레이션을 다시 만들어 큰 마이그레이션 0017 과 함께 운영 데이터베이스에서 돌렸다."},"severity":"unknown","symptoms":["deploys-failing","node-disk-full"],"timeline":{"detected_after":null,"resolved_after":"63d"},"date_kind":"incident","detection":{"en":"The sprint 82 production deployment failed with a disk space error, and the large migration search_indexes 0017 was identified as the cause.","ko":"운영 배포가 실패하면서 드러났다. 9월 7일 sprint 80 배포와 9월 20일 sprint 82 배포가 차례로 실패했고, 9월 20일 배포에서 디스크 공간 오류가 났다."},"components":["database","schema-migration"],"root_cause":"deploy-rollout","remediation":[{"text":{"en":"Increased the production database instance size and re-ran the migrations","ko":"11월 8일 운영 데이터베이스 인스턴스 크기를 키우고 마이그레이션을 다시 돌렸다. 이번에는 빈 문자열을 정수로 바꾸는 오류로 마이그레이션이 실패했다."}},{"text":{"en":"Created a backup of the production database","ko":"운영 데이터베이스의 백업을 만들었다."}},{"text":{"en":"Truncated all of the search_indexes tables","ko":"`search_indexes` 테이블을 모두 비웠다. 팀은 여기서 운영 데이터를 잃는 것을 걱정하지 않았다."}},{"text":{"en":"Redeployed the sprint 82 release","ko":"sprint 82 릴리스를 다시 배포해 운영을 sprint 82 로 되살렸고, 배포가 다시 가능해졌다."}},{"text":{"en":"Fixed the bug that stored empty fields as blank strings","ko":"빈 문자열 버그를 고쳤다."}}],"action_items":[{"text":{"en":"Write troubleshooting steps for database problems into the technical documentation","ko":"데이터베이스 문제 해결 절차를 기술 문서에 더한다."}},{"text":{"en":"Write wiki pages and operations playbooks that outline common problems and responses","ko":"위키 페이지와 운영 플레이북에 흔한 문제와 대응을 정리한다."}},{"text":{"en":"Add a database section and the cf revisions command to the failed-deployment troubleshooting document","ko":"Failed-Deployment-Troubleshooting 문서에 데이터베이스 문제 절을 만들고 cf revisions 명령을 적는다."}},{"text":{"en":"Keep a GitHub repository of changes made directly to production","ko":"운영에 직접 한 변경을 모은 GitHub 레포를 만들어, 비슷한 문제가 다시 날 때 길잡이로 쓴다."}},{"text":{"en":"Save terminal output from production debugging sessions","ko":"운영 디버깅 세션의 터미널 출력을 저장한다."}},{"text":{"en":"Create Django migration best practices to enforce in code reviews","ko":"코드 리뷰에서 지킬 Django 마이그레이션 모범 사례를 만든다."}},{"text":{"en":"Do not edit migrations that have been merged to develop; write a new migration instead","ko":"develop 에 머지된 마이그레이션은 고치지 않고, 그 시점부터 모델을 바꾸는 새 마이그레이션을 쓴다."}},{"text":{"en":"Rename auto-generated migrations to more descriptive names","ko":"자동 생성된 마이그레이션 이름을 알아보기 쉬운 이름으로 바꾼다. 이름만 보고 내용을 알 수 없으면 조사하기 어렵다."}},{"text":{"en":"Use one auto-generated migration per model change and regenerate when a pull request makes successive changes","ko":"자동 생성 마이그레이션은 모델 변경 하나에 하나만 두고, PR 하나에서 모델을 여러 번 바꾸면 마이그레이션을 다시 생성한다."}},{"text":{"en":"Organise a discussion on database seeding and backups for testing","ko":"시험에 쓸 데이터베이스 시딩과 백업을 어떻게 할지 논의 자리를 만든다."}}],"contributing":["overlapping-failures","test-coverage-gap","staging-not-representative","no-runbook"],"recurrence_of":null,"symptoms_text":{"en":"On 20 September 2023 the sprint 82 production deployment of TANF-app failed with a disk space error while it ran a large search-index migration, search_indexes 0017, against the production PostgreSQL database on cloud.gov. Production could not be deployed again until 9 November.\n\nThe deployment trouble had begun on 7 September with the failed sprint 80 deployment. A search_indexes migration that had already been deployed, 0016, was deleted, and makemigrations and migrate were commented out in the deployed environments to force manual migrations. The sprint 82 release uncommented them and recreated the broken 0016. Between the two deployments, develop pipelines were blocked for various reasons, and after 20 September staging problems kept the release from reaching production.\n\nOn 1 November the team tried to reproduce the failure in its raft environment with 700k to 1m rows each of t1, t2, t3, t4 and t5. During the migrations the database size grew up to 8x and fell back to its starting level when they finished, and temp_files grew by about 4gb after completion, but the migrations ran even with far more records than production. The team put this down to a difference in some database settings. On 6 November, lowering the raft database instance storage to 20gb reproduced the failure after several migration runs, as the temp space eventually exceeded the instance size.\n\nOn 8 November the production database instance size was increased and the migrations were run again. They then failed with a new error about converting a blank string to an integer. Every character field in T3 had some records with the value '' (blank string) instead of None.","ko":"2023년 9월 7일 TANF-app 의 sprint 80 운영 배포가 실패했고, 9월 20일 sprint 82 배포는 디스크 공간 오류로 실패했다. 그 뒤 11월 9일까지 운영에 배포하지 못했다.\n\n9월 7일 배포에서 팀은 이미 배포된 마이그레이션 search_indexes 0016 을 지웠고, 배포 환경에서 `makemigrations` 와 `migrate` 를 주석 처리해 마이그레이션을 손으로 돌리게 했다. 9월 20일 배포는 이 주석을 풀고 망가진 0016 을 다시 만들었는데, 이때 디스크 공간 오류가 났고 큰 마이그레이션인 search indexes 0017 이 드러났다. 두 배포 사이에는 develop 파이프라인이 여러 이유로 막혀 있었고, 9월 20일 뒤에는 staging 문제가 운영 배포를 막았다.\n\n11월 1일 팀은 raft 환경에 t1, t2, t3, t4, t5 를 각각 70만~100만 행 올리고, 로컬 환경에서 배포된 데이터베이스를 대상으로 마이그레이션을 돌려 문제를 재현해 보았다. 마이그레이션 중 데이터베이스 크기는 최대 8배까지 늘었다가 끝나면 처음 크기로 돌아왔고, `temp_files` 는 끝난 뒤 약 4gb 늘어 있었다. 레코드 수가 운영보다 훨씬 많은데도 마이그레이션은 성공했고, 팀은 데이터베이스 설정 차이 때문이라고 보았다.\n\n11월 6일 raft 환경의 데이터베이스 인스턴스 저장 공간을 20gb 로 낮추자, 마이그레이션을 여러 번 돌린 끝에 임시 공간이 인스턴스 크기를 넘어 문제가 재현됐다.\n\n11월 8일 운영 데이터베이스 인스턴스를 키우고 마이그레이션을 다시 돌리자, 빈 문자열을 정수로 바꾸다 실패하는 새 오류가 났다. 해당 마이그레이션에 의심 가는 곳이 많아 손으로는 빈 필드를 찾지 못했고, \"Debugging commands\" 의 \"check for blank strings in CharFields\" 함수로 T3 의 필드들을 짚어 냈다. T3 의 모든 문자 필드에 `None` 이 아니라 `''` 가 든 레코드가 있었다."},"updated_at":"2026-11-14T12:34:16.642058Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0523","org_slug":"u-s-department-of-health-and-human-services"}