IL-0456 Inflab · 2022-01-03databaseaws
100% 할인 이벤트 기간에 인프런 서비스가 Aurora PostgreSQL 병목으로 여섯 번 멈췄다
인프랩은 2022년 1월 3일부터 17일까지, 이어서 18일부터 24일까지 강의 125개를 0원에 등록하는 100% 할인 이벤트를 열었다. 이벤트가 트위터와 여러 IT 커뮤니티에 퍼지며 평소 대비 5~20배의 트래픽이 들어왔고, 7일 만에 76,730명이 새로 가입했다. 인프런은 모놀리식 NodeJS 프로젝트 하나를 ECS Fargate 에서 10대에서 30대로 돌린다. 데이터베이스는 쓰기 1대와 읽기 2~7대로 이루어진 Aurora PostgreSQL 10.16 이다. 서비스별로 DB 가 나뉘어 있지 않아, 한 기능의 장애가 전체 서비스 장애로 번지는 구조였다.
1월 3일에는 관심회사 조회 API 의 인덱스 없는 쿼리가 Node 프로세스의 커넥션을 고갈시켜 1시간 30분 동안 서비스가 멈췄다. 팀은 인덱스를 추가해 이 장애를 넘겼다. 같은 날 밤에는 쓰기 DB CPU 가 90% 넘게 올라 서비스가 2시간 동안 멈췄다. 모든 캐시를 담은 용량이 큰 캐시 행 1건의 초기화와 재생성, 그리고 쓰기 DB 에서 돌던 조회 쿼리 때문이었다. 팀은 캐시 주기를 바꾸고 조회 쿼리를 읽기 DB 로 옮겼다. 1월 4일 오전에는 강의 조회 슬로우 쿼리로 서비스가 3시간 동안 멈췄다. DB 를 r5.8xlarge 8대로 늘려도 몇십 분만 버텼다. 팀은 인덱스를 못 타는 쿼리를 고치고, select * 에서 강의 소개 컬럼을 뺐다. 원인 쿼리는 30분 안에 찾았다. 그러나 쿼리가 함수, Hook, 커스텀 이벤트로 감싸여 추상화돼 있었고 코드에 타입과 테스트가 없어 고치는 데 3시간이 걸렸다. 1월 7일에는 ID 가 100개를 넘는 IN 절이 풀 스캔을 했다. 팀은 ID 를 20개씩 나눠 보내게 고쳤다. 두 번째 이벤트 전에는 어느 페이지가 문제가 될지 몰라 성능 테스트를 할 수 없었다. 그래서 PostgreSQL 을 11.13 으로 올리고, 쿼리 타임아웃 5초를 걸고, Max Connection 을 늘리고, 2초 넘는 쿼리를 개선했다.
1월 17일 오후 10시 30분에 여섯 번째 장애가 났다. 행이 1만 건도 안 되는 강의 테이블만 읽는 쿼리도 느렸다. 쓰기 DB 의 Tuple 정보를 보니 강의 테이블의 Dead Tuple 이 99% 였다. 수동 vacuum 뒤에도 Dead Tuple Rate 는 매우 빠르게 다시 올랐다. 강의 테이블에는 수강생 수 컬럼이 있었고, 수강 신청이 들어올 때마다 이 값을 1 올리는 UPDATE 가 실행됐다. PostgreSQL 은 MVCC 때문에 UPDATE 때마다 기존 행을 남겨 두고 새 행을 쓴다. 10KB 짜리 강의 소개 HTML 컬럼을 가진 행이 계속 갱신되면서 용량이 큰 Dead Tuple 이 대량으로 쌓였다. autovacuum 이 이를 따라가지 못해 강의 테이블을 읽는 모든 쿼리가 병목에 걸렸다. 팀은 강의 소개 컬럼을 별도 테이블로 분리했고, 그 뒤로 이벤트로 인한 장애는 더 나지 않았다.
- 심각도
- –
- 영향 범위
- Inflearn was unusable at peak hours on 3, 4, 7 and 17 January 2022, six outages in total, including 1 h 30 min and 2 h on 3 to 4 January and 3 h on the morning of 4 January
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- –
증상
2022년 1월 3일부터 17일까지 이어진 100% 할인 이벤트 기간에, 인프런은 1월 3일, 4일, 7일, 17일 오전 9시와 오후 10시 무렵마다 쓸 수 없을 만큼 느려졌다. 트래픽은 평소의 5~20배였고, 매번 데이터베이스의 슬로우 쿼리와 부하가 함께 나타났다.
1월 3일 오후 8시 30분부터 10시까지는 모든 강의 소개 페이지가 부르는 관심회사 조회 API 에서 슬로우 쿼리가 대량으로 났다. 쿼리가 밀리자 Node 프로세스의 커넥션이 빠르게 고갈됐고, 뒤이은 요청이 모두 대기하다가 Node 프로세스가 강제 종료됐다. ECS 가 프로세스를 다시 채웠지만, 종료된 프로세스가 처리하던 다른 쿼리도 전부 취소돼 서비스 전체가 멈췄다.
1월 3일 오후 11시 30분부터 1월 4일 오전 1시 30분까지는 쓰기 DB 의 CPU 가 90% 이상으로 올라 전체 서비스가 멈췄다. 모든 캐시 데이터를 JSON 하나로 담은 용량이 큰 캐시 행 1건에 캐시 초기화와 재생성 요청이 겹쳐 데드락이 났다. 아직 읽기 DB 로 옮기지 못한 조회 쿼리도 쓰기 DB 의 부하를 키웠다.
1월 4일 오전 9시부터 12시까지는 강의 조회 쿼리가 슬로우 쿼리 상위 9개를 모두 차지했다. lower("컬럼명") = lower(쿼리파라미터) 처럼 컬럼을 변형하거나 !=, NOT IN 같은 부정 조건을 써서 인덱스를 타지 못하는 쿼리가 많았다. 그 결과 테이블 풀 스캔이 동시에 수십에서 수백 개씩 돌았다. 값 하나가 10KB 인 강의 소개 컬럼까지 select * 로 읽는 쿼리는 READ IO 병목을 일으켰다. 데이터베이스를 r5.8xlarge 8대로 늘렸지만 몇십 분을 버텼을 뿐 장애를 해소하지는 못했다.
1월 7일 오후 9시부터는 수강바구니, 구매완료, 결제페이지에서 강의 정보를 가져오는 where id in (ID) 쿼리가 ID 를 100개 넘게 받아 인덱스를 쓰지 못하고 테이블 풀 스캔을 했다. 이벤트 강의 125개를 한 번에 담아 일괄 결제하는 수강생이 급증했기 때문이었다.
1월 17일 오후 10시부터 RDS 의 여러 수치가 요동쳤고, 오후 10시 30분에 1월의 여섯 번째 장애가 시작됐다. 행이 1만 건도 안 되는 강의 테이블만 읽는 쿼리조차 느려졌다. 그때까지 병목은 모두 읽기 DB 의 슬로우 쿼리로 보였고, 팀은 읽기 DB 만 모니터링하고 있었다.
원인
- 촉발
- 2022년 1월 강의 125개를 0원에 등록할 수 있는 100% 할인 이벤트가 크게 퍼지면서 평소 대비 5~20배의 트래픽이 들어왔다.
감지
모니터링과 슬로우 쿼리 알람으로 문제를 알아챘고, 문제가 되는 쿼리를 30분 안에 찾았다. 1월 17일에는 오후 10시부터 RDS 의 여러 수치가 요동치는 것을 보았다.
조치
- 관심회사 조회 API 의 쿼리 일부가 인덱스를 쓰지 않는 것을 로그와 실행계획으로 확인하고 바로 인덱스를 추가했다.
- 용량이 큰 캐시 행의 캐시 초기화와 재생성 주기를 바꿨다.
- 쓰기 DB 에 부하를 준 조회용 쿼리부터 읽기 DB 로 옮겼다.
- 시간을 벌려고 데이터베이스를 r5.8xlarge 8대로 늘렸다.
- 느린 강의 조회 쿼리에 인덱스를 추가하거나 쿼리를 바꿔 인덱스를 쓰게 했고, 개선할 수 없는 쿼리를 쓰는 기능은 껐다.
- select * 로 읽던 강의 조회 쿼리에서 실제로 쓰지 않는 고용량 강의 소개 컬럼을 모두 뺐다.
- IN 절에 담는 강의 ID 를 20개씩 나눠 Promise.all 로 처리하게 바꿔, 쿼리가 다시 인덱스를 쓰게 했다.
- PostgreSQL 을 10.16 에서 11.13 으로 올렸다.
- 5초 넘게 도는 쿼리를 강제로 끝내도록 쿼리 타임아웃을 5초로 걸었다.
- Max Connection 을 늘렸다.
- 강의 테이블에 수동으로 vacuum 을 돌려 Dead Tuple 을 정리했다.
- 용량이 큰 강의 소개 컬럼을 강의 테이블에서 떼어 별도 테이블로 옮겼다.
후속 조치
- 한 테이블의 장애가 전체 서비스의 장애로 번지는 구조를 개선한다.
- 성능 개선이 쉽지 않은 테이블과 캐시 구조를 개선한다.
- 직관적이지 않은 SQL 코드를 개선한다.
- 타입과 테스트 코드가 없어 안심하고 고치기 어려운 코드를 개선한다.
- 배치 작업을 안정적으로 돌릴 환경을 갖춘다.
- 모놀리식 구조는 알맞은 선에서 유지하고, 장애가 잘 번지는 일부 도메인만 따로 분리한다.
교훈
그동안 데이터베이스 병목이 모두 읽기 DB 의 슬로우 쿼리로 보여 팀은 읽기 DB 만 모니터링했다. 이 때문에 근본 원인인 쓰기 DB 강의 테이블의 Dead Tuple 을 늦게 찾았다. 원인 쿼리는 빨리 찾았지만, 무분별한 쿼리 추상화와 타입과 테스트 코드가 없는 코드 때문에 수정과 배포가 늦어졌다. 그만큼 장애 시간도 길어졌다. 첫 주 장애를 돌아보면 어느 페이지가 문제가 될지 예상할 수 없었고, 대부분의 페이지가 트래픽이 몰리면 언제든 장애가 날 수 있는 상태였다. 팀은 조직의 구조와 성숙도가 갖춰지지 않은 상태에서 마이크로서비스 아키텍처인 MSA 로 바로 옮기면 더 큰 문제가 생긴다고 보았다. 그래서 모놀리식 구조를 알맞은 선에서 유지하면서 일부 도메인만 분리하기로 했다.
출처
관련 사례
같은 원인 · 공통 데이터베이스, PostgreSQL vacuum
같은 원인 · 공통 데이터베이스, PostgreSQL vacuum
IL-0211Mandrill 의 PostgreSQL 샤드 하나가 트랜잭션 ID 랩어라운드를 막으려고 쓰기를 멈춰 약 40시간 동안 메일 발송이 줄었다Mailchimp · 2019같은 원인 · 공통 데이터베이스, PostgreSQL vacuum
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0288CDN77 의 ClickHouse 데이터 파이프라인에서 이중화된 인스턴스 세 곳의 ZooKeeperWait 가 수천 초로 치솟아 복제가 한 시간 넘게 느려졌다CDN77 · 2026같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0305Intercom 의 PlanetScale 샤드 하나에서 복제본이 CPU 100% 로 주 데이터베이스를 따라가지 못해 받은편지함에 최대 2.2시간 전의 데이터가 보였다Intercom · 2026같은 원인 · 공통 데이터베이스
IL-0390PostHog US Cloud 의 Persons Postgres 데이터베이스 쓰기가 크게 느려져 이벤트와 persons 수집이 최대 2일 지연됐다PostHog · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0287Atlas 와 Cloud Manager 의 모니터링을 받치는 내부 데이터베이스 클러스터의 샤드 하나가 과부하되어 지표 수집과 클러스터 작업이 늦어졌다MongoDB · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0298Wikimedia sessionstore 의 Cassandra 노드가 디스크가 가득 차 잇따라 죽고 쿼럼을 잃어 편집이 실패했다Wikimedia Foundation · 2025같은 원인 · 공통 데이터베이스
IL-0312max_connections 를 올리려고 재시작한 주 PostgreSQL 데이터베이스가 다시 올라오지 않아 Gadget 의 모든 앱 요청이 멈췄다Gadget · 2024같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0110Grafana Agent 파드가 메모리를 많이 쓰던 노드에 Hangfire 파드가 함께 올라가 시스템 OOM 으로 CrashLoopBackOff 를 반복했고, 노드가 응답을 멈추고 NotReady 가 됐다.DFDS · 2024같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0202갑자기 몰린 쿼리와 데이터베이스 쓰기로 Honeycomb 수집 호스트가 메모리 부족으로 죽었고, 쿠버네티스 CrashLoopBackOff 가 용량 회복을 막아 US1 이벤트 수집이 한 시간 넘게 불안정했다Honeycomb · 2023 · sev1같은 원인 · 공통 데이터베이스
IL-0386Google Cloud 로 옮긴 뒤 GKE 노드에서 Postgres 연결 타임아웃과 memcached i/o timeout 이 간헐적으로 튀었다incident.io · 2023같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0206GKE 파드 안의 Consul 에이전트가 메모리 급증으로 거듭 OOM kill 되어 Rails 파드가 데이터베이스 복제본을 찾지 못했고, 모든 쿼리가 primary 로 몰려 GitLab.com 이 느려졌다.GitLab · 2022같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0496전역 복제 Cassandra 클러스터에서 모든 리전의 인스턴스 CPU 가 100% 로 치솟아 쿼리가 타임아웃됐고, 같은 부하가 이틀 저녁 더 찾아왔다Ably · 2020같은 원인 · 공통 데이터베이스
IL-0443Aurora MySQL reader 가 이유 없이 재시작한 뒤 writer 의 insert latency 가 7배쯤 오르고 replication lag 가 하루 넘게 이어졌다Repro · 2019같은 원인 · 공통 데이터베이스
IL-0037imagePullPolicy가 Always인 DaemonSet이 크래시 루프를 돌며 이미지를 계속 받아, NAT 세 대를 같이 쓰던 리전 전체가 이미지 레지스트리에서 429로 막혔다Datadog · 2019같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0064CPU 를 많이 쓰는 파드가 Google Cloud 노드 하나에 몰려 노드가 커널 패닉으로 죽었고, 다시 배치된 파드가 다음 노드를 또 죽이며 웹사이트와 API 가 멈췄다.Moonlight · 2019같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0334RDS MySQL 의 bookmarks 테이블이 2TB 파일 크기 한도에 닿아 INSERT 가 실패하고 Instapaper 가 31시간 동안 멈췄다Instapaper · 2017같은 원인 · 공통 데이터베이스
IL-0333사양을 낮춘 RDS PostgreSQL 인스턴스가 피크 부하를 못 버텨 헬스 체크가 실패하고 Buildkite 대시보드가 몇 시간 동안 내려갔다Buildkite · 2016같은 원인 · 공통 데이터베이스
IL-0282US-East 리전의 DynamoDB 스토리지 서버들이 멤버십 정보를 받지 못해 스스로 요청 처리에서 빠지면서 고객 요청의 약 55% 가 실패했다Amazon Web Services · 2015같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0456
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0456/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.