{"id":"IL-0510","org":"MIT Open Learning","date":"2026-03-24","docs":["https://www.postgresql.org/docs/current/runtime-config-resource.html","https://www.postgresql.org/docs/current/runtime-config-logging.html","https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PostgreSQL.optimizedreads.html"],"title":{"en":"MIT Learn's RDS PostgreSQL ran out of storage after a release and learn.mit.edu returned 503s and 504s for hours","ko":"MIT Learn 운영 RDS PostgreSQL 의 저장 공간이 임시 파일로 가득 차 learn.mit.edu 가 세 시간 동안 503 과 504 를 냈다"},"domain":["database","aws"],"impact":{"scope":"learn.mit.edu returned 503 and 504 errors while the production database was out of storage","users":"unknown","region":"us-east-1"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"The change was reviewed by senior engineers and showed no problems locally or on RC, but data scale and load in production are very different, and nothing in the system's health showed that adding a COUNT aggregation would make disk usage explode.","ko":"이 변경은 가장 시니어인 엔지니어 두 명이 꼼꼼히 리뷰했지만 로컬과 RC 환경에서는 아무 문제도 드러나지 않았다. 운영의 데이터 규모와 시스템 부하가 크게 달랐고, 효율적이라고 여긴 COUNT 집계가 디스크 사용량을 폭증시킬 것이라는 점은 당시 시스템 상태만 보고는 알기 어려웠다. 그래서 운영과 같은 규모의 데이터로 부하를 시험할 환경이 필요하다고 팀은 결론지었다.\n\nAWS 지원은 ORM 쿼리 변경을 운영 수준의 데이터를 담은 스테이징에서 시험하고, EXPLAIN ANALYZE 로 실행 계획과 예상 자원 사용을 보며, 시험 중 log_temp_files 로 임시 파일 생성을 지켜보라고 권했다. 폭주하는 쿼리를 막도록 temp_file_limit 를 설정하고, 실행 계획에 따라 work_mem 이나 shared_buffers 를 조정하되 메모리를 너무 쓰면 out of memory 로 장애 조치가 일어날 수 있다는 점도 짚었다. 임시 파일을 많이 쓰는 작업에는 임시 파일을 EBS 대신 로컬 NVMe 에 두는 RDS Optimized Reads 를 고려하라고 했다."},"related":[],"sources":[{"url":"https://engineering.ol.mit.edu/runbooks_post_mortems/20260324_mitlearn_outage/","kind":"runbook","archived":null}],"summary":{"en":"The learning resource APIs of MIT Learn were slow, especially the featured resources API, and Sentry pointed at a large query that fetched all view events to count them. An engineer changed the query pattern so that the main query contained an aggregation. The team's two most senior engineers reviewed the change, it showed no issues locally or on the RC environment, and it went out in a regular release. DRF computes the count field of a paginated response by running SELECT COUNT * ($QUERY) over the whole query; with the aggregation inside, this worked on smaller datasets but not on production data. On the RDS PostgreSQL instance ol-mitlearn-db-production in us-east-1, the query thrashed temporary files, filled the storage and backed up the database's internal work so that new transactions could not be processed.\n\nThe change was rolled back and the database was declared up at 4:31, but the site returned 503 errors again at 4:51. The team deleted the read replica so the database could flush buffered WAL files, dropped the replication slot and ran VACUUM ANALYZE, without bringing DiskQueueDepth down. After an RDS backup finished, and with help from AWS Support, the team terminated every active query that had been running for more than 10 minutes with pg_terminate_backend. Free storage and DiskQueueDepth then returned to normal around 6:24 PM. The write-up gives the total outage as 3 hours and 35 seconds.\n\nAfterwards the team changed the view count to use a precomputed column, and response times of the affected APIs went from ~13-15s to 150-200ms. AWS Support recommended testing ORM query changes with production-like data volumes and EXPLAIN ANALYZE, setting temp_file_limit and log_temp_files, and considering RDS Optimized Reads, which place temporary files on local NVMe storage instead of EBS.","ko":"학습 자료 API, 그중에서도 추천 자료 API 가 느렸고, Sentry 는 조회 이벤트를 전부 가져와 건수를 세는 큰 쿼리를 가장 유력한 원인으로 지목했다. 이를 고치려는 쿼리 패턴 변경이 시니어 엔지니어 두 명의 리뷰를 거쳐 정기 릴리스로 배포됐다. 변경은 메인 쿼리 안에 집계를 넣었는데, DRF 가 페이지 응답의 건수를 구하려고 그 쿼리 전체를 COUNT 로 감쌌다. 로컬과 RC 환경처럼 데이터가 작은 곳에서는 문제가 없었지만, 운영에서는 Postgres 서버의 임시 파일이 저장 공간을 모두 채웠다. 18:35 UTC 에 시작된 이 상태에서 WAL 과 체크포인트가 밀리며 새 트랜잭션을 처리하지 못했고, learn.mit.edu 는 503 과 504 를 냈다.\n\n팀은 Qdrant, pgdump 스냅샷 순으로 의심하다가 릴리스의 쿼리 변경을 원인으로 확인하고 롤백했다. 데이터베이스가 안정됐다고 본 뒤에도 503 이 다시 났다. 읽기 복제본 삭제, 복제 슬롯 삭제, VACUUM ANALYZE 를 차례로 했지만 DiskQueueDepth 는 계속 올랐다. AWS 지원과 연결된 상태에서 백업이 끝났고, 엔지니어가 10분 넘게 돌던 쿼리를 pg_terminate_backend 로 종료하자 디스크 큐 깊이와 남은 저장 공간이 정상으로 돌아왔다. 장애는 모두 3시간 35초 동안 이어졌다.\n\n그 뒤 팀은 조회 수를 미리 계산해 둔 열에서 읽도록 고쳐 배포했고, 영향을 받던 API 의 운영 응답 시간이 약 13~15초에서 150~200ms 로 줄었다."},"trigger":{"en":"A regular release deployed a query pattern change to the learning resource APIs that put an aggregation inside the main query.","ko":"정기 릴리스 절차로 배포한 변경이 페이지 응답의 메인 쿼리 안에 집계를 넣었다. DRF 는 페이지 응답의 count 칸을 SELECT COUNT * ($QUERY) 로 계산하므로, 운영의 데이터 규모에서 이 쿼리가 임시 파일을 대량으로 만들었다."},"severity":"unknown","symptoms":["service-unavailable","node-disk-full","database-overloaded","lock-contention"],"timeline":{"detected_after":null,"resolved_after":"3h"},"date_kind":"incident","detection":{"en":"An engineer reported the outage with a linked Sentry issue, and Sentry showed many 504 responses. The RDS FreeStorageSpace metric showed that the database had used up its storage.","ko":"엔지니어가 장애를 보고하며 Sentry 이슈를 연결했다. 이어 다른 엔지니어가 운영 데이터베이스의 저장 공간이 바닥난 것을 확인했고, FreeStorageSpace 지표와 Sentry 에 쌓인 504 도 같은 상황을 보여 줬다."},"components":["database"],"root_cause":"deploy-rollout","remediation":[{"text":{"en":"Rolled back the release's query pattern change","ko":"쿼리 패턴 변경을 쓴 엔지니어가 그 변경이 원인임을 확인하고 릴리스를 롤백했다."}},{"text":{"en":"Deleted the read replica so the database could flush its buffered WAL files","ko":"데이터베이스가 버퍼에 쌓인 WAL 파일을 따라잡아 비울 여유를 주려고 읽기 복제본을 삭제했다."}},{"text":{"en":"Terminated all active queries running longer than 10 minutes with pg_terminate_backend, after the backup finished and with guidance from AWS Support","ko":"AWS 지원의 도움을 받아 백업이 끝난 뒤 오래 돌던 쿼리를 안전하게 종료했다. pg_stat_activity 에서 state = 'active' 이고 시작한 지 10분이 넘은 쿼리를 pg_terminate_backend(pid) 로 끝내자 저장 공간과 처리 여유가 돌아왔다."}}],"action_items":[{"text":{"en":"Explore enabling enhanced analytics on the production Learn database","ko":"운영 learn 데이터베이스에 enhanced analytics 를 켜는 방안을 검토한다."}},{"text":{"en":"Document rollback steps on the platform engineering documentation site","ko":"플랫폼 엔지니어링 문서 사이트에 롤백 절차를 적는다."}},{"text":{"en":"Make easy rollback part of the new release management framework","ko":"장기적으로는 새로 만드는 릴리스 관리 체계에서 롤백을 쉽게 할 수 있게 한다."}},{"text":{"en":"Alert on DiskQueueDepth for production databases without making the alert too noisy","ko":"운영 데이터베이스의 DiskQueueDepth 에 알림을 걸되 너무 자주 울리지 않게 한다."}},{"text":{"en":"Alert when API and site response times are more than 30% above normal","ko":"API 와 사이트의 응답 시간이 평소보다 30% 넘게 높아지면 알림을 보낸다."}},{"text":{"en":"Run a gameday on an example production database outage","ko":"운영 데이터베이스 장애를 예제로 삼아 게임데이 훈련을 한다."}},{"text":{"en":"Adopt Rootly for incident management, with an automatic channel and an automatic incident transcript","ko":"장애 관리 도구 Rootly 를 써서 장애 때 소통을 한 채널에 자동으로 모으고 장애 기록을 자동으로 남긴다."}},{"text":{"en":"Write an outage runbook: start a call, have the on-call engineer watch metrics until they return to baseline, and give one person the communications","ko":"장애 대응 런북을 가볍게 만든다. 장애가 나면 Zoom 통화를 열고, 당번 엔지니어가 지표가 평소 수준으로 돌아오는지 지켜보며, 소통 담당을 따로 두어 이해관계자의 문의가 핵심 대응자를 방해하지 않게 한다."}},{"text":{"en":"Put the query that terminates long-running queries, and a version that finds them for selective termination, into a production Postgres runbook","ko":"오래 도는 쿼리를 종료하는 SQL 과, 이 SQL 을 고쳐 아주 오래 도는 쿼리만 골라 종료하는 SQL 을 운영 Postgres 런북에 넣는다."}},{"text":{"en":"Open an issue on redesigning the Learn Resource View Event model, which is only used for counts","ko":"Learn Resource View Event 모델이 지금은 건수를 세는 데만 쓰이므로 다시 설계해야 한다는 이슈를 쓴다."}},{"text":{"en":"Use anonymised production data generation tools to bring RC up to production data volume, on separate infrastructure for load testing","ko":"익명화한 운영 데이터 생성 도구로 RC 환경의 데이터 양을 운영 수준까지 늘린다. 운영 데이터를 익명화해 담은 운영 규모의 부하 시험 환경을 만들고, 이해관계자도 시험에 쓰는 RC 에 영향이 없도록 인프라를 분리한다."}},{"text":{"en":"Change the view count to use a precomputed column","ko":"조회 수를 미리 계산해 둔 열에서 읽도록 고쳐 배포했다. 영향을 받던 API 의 운영 응답 시간이 약 13~15초에서 150~200ms 로 줄었다."}}],"contributing":["inefficient-query","staging-not-representative","manual-action-worsened"],"recurrence_of":null,"symptoms_text":{"en":"On 24 March 2026 learn.mit.edu went down shortly after a regular release. An engineer reported the outage with a linked Sentry issue, and Sentry soon showed many 504 responses from the site.\n\nThe MIT Learn production database had used up all of its usable storage, and the growth had started at 18:35 UTC. The PostgreSQL server was thrashing its temporary filesystem. The RDS FreeStorageSpace metric dropped, and later the CheckpointLag and DiskQueueDepth metrics looked unhealthy; the team treats DiskQueueDepth as the critical health metric for RDS.\n\nThe first suspects were the Qdrant cluster and a pgdump snapshot an engineer had just created, and both were ruled out within about ten minutes. The engineer who wrote the release's query pattern change then identified it as the cause. The database was declared up and stable at 4:31, but the site still returned 503 errors at 4:51, and when the site was reported back at 5:26, DiskQueueDepth was still unhealthy.\n\nDeleting the read replica so the database could flush its buffered WAL files, dropping the replication slot and running VACUUM ANALYZE did not bring DiskQueueDepth down. Dropping the slot had minimal effect, and VACUUM ANALYZE started a number of additional queries and may have made the problem worse.","ko":"learn.mit.edu 가 멈췄다는 보고가 Sentry 이슈 링크와 함께 올라왔다. MIT Learn 의 운영 데이터베이스인 RDS PostgreSQL 인스턴스가 쓸 수 있는 저장 공간을 모두 써 버린 상태였고, 사이트는 503 과 504 오류를 냈다.\n\n엔지니어 한 명이 FreeStorageSpace 지표를 공유했고, 저장 공간을 먹기 시작한 시점은 18:35 UTC 였다. 문제의 중심은 Postgres 데이터베이스 서버가 임시 파일 시스템에 쓰기와 읽기를 끝없이 되풀이한 것이었다. Sentry 에는 504 가 많이 쌓였다.\n\n데이터베이스가 올라와 안정됐다고 선언한 지 20분 뒤에 learn.mit.edu 에서 다시 503 이 났다. 그 뒤 CheckpointLag 지표가 위험해 보였고, 사이트가 돌아왔다는 첫 보고가 나온 뒤에도 RDS 상태를 보는 핵심 지표인 DiskQueueDepth 는 계속 나빴다.\n\n처음에는 Qdrant 클러스터를 의심했지만 곧 운영 데이터베이스 문제로 확인했다. 다음에는 한 엔지니어가 직전에 만든 pgdump 스냅샷을 의심했다가 원인이 아니라고 판단했다. 이어서 방금 배포한 릴리스에 들어간 쿼리 패턴 변경이 의심을 받았고, 변경을 쓴 엔지니어가 원인으로 확인해 롤백했다.\n\n롤백 뒤에도 바로 낫지 않아 여러 조치를 해 봤다. 버퍼에 쌓인 WAL 파일을 비울 여유를 주려고 읽기 복제본을 지웠고, 정리를 빠르게 하려고 복제 슬롯을 지웠지만 효과는 거의 없었다. VACUUM ANALYZE 로 공간을 되찾으려 했으나 DiskQueueDepth 는 계속 올랐고, 이 명령이 쿼리를 더 일으켜 오히려 문제를 키웠을 수 있다."},"updated_at":"2026-11-14T12:34:15.450617Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0510","org_slug":"mit-open-learning"}