사례2건
2건 중 1~2번째
IL-0316밀린 배포가 한꺼번에 몰린 Control Plane 에서 새 버전 PgBouncer 가 CPU 와 메모리를 비정상적으로 많이 써 데이터베이스 연결이 타임아웃됐다Railway2025- 증상
2025-09-22 11:05 UTC 에 Railway 프로젝트 캔버스가 열리지 않는다는 첫 신고가 들어왔다. 캔버스는 실제 오류를 보여 주지 않고 끝없이 로딩 상태에 머물렀다.
- 촉발
코드 정리 중 쓰지 않는 컬럼을 지우는 스키마 변경을 했는데, 뒤이은 코드 변경이 그 일부를 의도치 않게 되돌려 프런트엔드가 쓰는 핵심 API 가 실패했다. 이 문제를 고친 뒤 밀린 배포가 한꺼번에 시작되면서, 몇 버전 올라간 PgBouncer 를 쓰던 Control Plane 이 부하를 이기지 못했다.
- 원인
- 구성 요소
- 기여 요인
- 감지
11:05 UTC 에 사용자들이 Railway 프로젝트 캔버스가 열리지 않는다고 신고했다. 두 번째 문제는 11:48 UTC 에 Control Plane 의 자원 사용이 비정상으로 늘고 API 지연이 높아지면서 드러났다.
- 조치
- 사고를 선언하고 15분 안에 수정을 배포해 프로젝트 캔버스 문제를 풀었다.
- 먼저 Free, Trial, Hobby 등급 사용자의 배포를 멈춰 Pro 사용자는 평소대로 배포하게 하고 배포 대기열이 차츰 줄게 했다.
- 가장 불안정할 때는 Pro 를 포함한 모든 새 배포를 최대 20분 멈춰 대기열을 더 줄였다.
- Control Plane 데이터베이스의 CPU 와 메모리를 늘리고, 커넥션 한도를 조정하고, IOPS 용량을 늘렸다.
- 설정을 손보는 대신 안정적이던 이전 PgBouncer 버전으로 곧바로 롤백했고, 성능이 100배 좋아지며 모든 지표가 정상으로 돌아왔다.
- 롤백 뒤 Pro 가 아닌 사용자 모두의 배포를 다시 열었다.
- 21:27 UTC 에 사설 네트워크와 그 엔드포인트가 대시보드에 보이지 않던 드문 경우를 뒤늦게 고쳤다.
- 후속 조치
- Control Plane 저장 계층의 큰 부분을 Postgres 밖으로 옮기는 재설계를 계속한다. 이 작업은 3분기 초에 시작했고, 한 리전의 Control Plane 장애가 플랫폼 전체에 번지는 것을 막고 지금의 Postgres 보다 몇 자릿수 높은 IOPS 를 낼 것을 목표로 한다.
- CI 파이프라인 검증을 강화해 중단을 일으키는 스키마 변경이 운영에 닿지 못하게 하고, 백엔드 배포 전에 스키마 호환성을 자동으로 검사한다.
- PgBouncer 노드의 CPU, 메모리, 커넥션 풀 사용률을 모니터링하고 알림을 건다.
- PgBouncer 업그레이드는 한꺼번에 하지 않고 인스턴스마다 단계적으로 적용한다.
- 교훈
Railway 는 호환되지 않는 스키마 변경이 운영에 닿지 않았다면 이번 연쇄 장애가 없었을 것으로 보았다. PgBouncer 의 성능 저하를 미리 알았다면 훨씬 빨리 복구할 수 있었다고 했다. PgBouncer 를 한꺼번에 올리지 않고 인스턴스마다 차례로 올렸다면 전체에 퍼지기 전에 버전별 성능 문제를 찾았을 것이라고도 했다.
- 증상
처리되지 않은 잡이 밀리는지 감시하던 DataDog 에서 알림이 울렸다. 대량의 잡이 처리되지 못하고 쌓여 있었다.
- 촉발
Redash 에서 reader 인스턴스로 보내야 할 데이터 분석용 쿼리가, 데이터 소스 등록 때 잘못 설정한 클러스터 엔드포인트를 거쳐 writer 인스턴스에서 오래 실행됐다.
- 원인
- 구성 요소
- 기여 요인
- 감지
처리되지 않은 잡이 밀리는지 감시하던 DataDog 알림이 대량의 잡이 쌓였을 때 울렸다.
- 조치
- Redash 의 데이터 소스 설정 실수를 고쳐 reader 엔드포인트를 가리키게 했다.
- VACUUM 을 막는 쿼리라고 확신한 뒤 pg_terminate_backend 로 그 쿼리의 프로세스를 강제로 끝냈다.
- 프로세스가 끝난 것을 확인하고 VACUUM 을 다시 수동으로 실행해 dead tuples 를 대량으로 치웠다.
- 후속 조치
- Redash 컨테이너의 /etc/hosts 에서 writer 엔드포인트 호스트 이름을 테스트용 무효 주소인 TEST-NET-1 의 IP 주소로 지정해, Redash 에 writer 엔드포인트를 잘못 설정해도 실제로는 접속하지 못하게 한다.
- PostgreSQL 사용자의 statement_timeout 을 알맞게 설정해 오래 걸리는 쿼리를 자동으로 멈춰 재발을 막는다.
- 늘어날 잡을 견디도록 qg 를 개선하거나, SELECT FOR UPDATE SKIP LOCKED 로 락을 잡는 river 같은 기술을 쓰는 것을 검토한다.
- 교훈
que_jobs 와 전혀 관계없는 쿼리라도 같은 DB 인스턴스에서 오래 실행되면 que_jobs 의 dead tuples 를 치우지 못하게 막아 테이블이 계속 커지고, qg 의 디큐 성능 저하와 DB 부하 상승으로 이어진다. 글쓴이는 이것을 오래 걸리는 쿼리 때문에 VACUUM 이 효과를 내지 못하는 PostgreSQL 운영의 전형적인 경우로 보았다. 이런 상황에서 어디를 확인해야 하는지를 정해 두고 습관으로 만들어야 한다고 느꼈다. 현재 qg 가 쓰는 advisory lock 과 Recursive CTE 는 dead tuples 의 영향을 받기 쉬운 설계다.
이 2건에서 가장 많이 나오는 구성 요소: 데이터베이스, PostgreSQL vacuum. kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이런 틈을 점검합니다. 점검 항목의 근거는 이 라이브러리와 같은 공개 보고서입니다.
원인별2
업그레이드로 깨진 호환성1건
- 조직
운영 실수1건
- 조직