IL-0582 Kanmu · 2024-12-08 (게시일)databaseaws
Aurora PostgreSQL 위의 qg 잡 큐에서 que_jobs 의 dead tuples 가 계속 늘어 잡이 대량으로 밀렸다
Kanmu 는 프로덕트 데이터베이스로 Amazon Aurora PostgreSQL 을 쓰고, 같은 데이터베이스를 Que 의 Go 구현인 qg 로 잡 큐로도 쓴다. 잡은 que_jobs 테이블의 행이고, worker 는 pg_try_advisory_lock 과 Recursive CTE 를 묶은 SQL 로 잡을 디큐하고 처리가 끝나면 행을 지운다. 어느 날 DataDog 에서 잡이 밀린다는 알림이 울렸다. 들어온 잡의 양은 평소와 같았지만 디큐 때의 lock_time 이 길어져 있었고, que_jobs 의 dead tuples 가 계속 늘고 있었다.
원인은 Redash 였다. 글쓴이가 예전에 Aurora 데이터베이스를 Redash 데이터 소스로 등록하면서 reader 엔드포인트가 아니라 writer 인스턴스를 가리키는 클러스터 엔드포인트를 설정했다. Redash 에서 보낸 무거운 분석 쿼리는 클라이언트 연결이 끊긴 뒤에도 writer 에서 계속 돌았고, 그 사용자에게는 statement_timeout 같은 쿼리 타임아웃이 없었다. 트랜잭션 격리 수준이 READ COMMITTED 여서 이 쿼리는 시작할 때 스냅샷을 만들었고, 그 뒤 que_jobs 에서 지워진 튜플도 이 쿼리에는 보이는 상태였다. 그래서 VACUUM 은 이 튜플들을 치우지 않았다. qg 의 디큐 SQL 은 queue 와 run_at 으로만 걸러서 이미 dead 인 행도 인덱스로 훑었고, 가시성을 확인하려고 매번 튜플을 가져왔다. 그 결과 디큐가 느려지고 데이터베이스 부하가 올랐다.
팀은 Redash 데이터 소스를 reader 엔드포인트로 고치고, pg_terminate_backend 로 문제의 쿼리를 끝낸 뒤 VACUUM 을 다시 실행했다. 이번에는 dead tuples 가 한꺼번에 치워졌고 worker 의 디큐 성능이 정상으로 돌아왔다.
- 심각도
- –
- 영향 범위
- Asynchronous jobs in the qg job queue on the product database piled up and were processed slowly
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- –
증상
처리되지 않은 잡이 밀리는지 감시하던 DataDog 에서 알림이 울렸다. 대량의 잡이 처리되지 못하고 쌓여 있었다.
이 잡들은 매일 거의 같은 시각에 들어오는 것이었고 양도 평소와 크게 다르지 않았다. 그런데 worker 가 잡을 디큐해 처리하는 시간이 평소보다 눈에 띄게 길었다. worker 로그에서는 잡을 가져오는 시간인 lock_time 이 평소보다 길어져 있었다.
DataDog 의 dead tuples 메트릭 postgresql.dead_rows 를 보자 que_jobs 테이블의 dead tuples 가 어느 시간대부터 계속 늘고 있었다. autovacuum 이 이를 처리해야 했지만 제대로 동작하지 않는 것으로 보였다.
수동으로 VACUUM VERBOSE que_jobs 를 실행하자 INFO: "que_jobs": found 0 removable, 768195 nonremovable row versions in 15623 out of 15623 pages 와 DETAIL: 679473 dead row versions cannot be removed yet, oldest xmin: 1226078887 이 나왔다. 지울 수 있는 행은 0개였고, dead tuples 679,473개가 지워지지 않았다.
pg_stat_activity 에서 활성 쿼리를 보자 사내 BI 도구 Redash 용 사용자가 보낸 의도하지 않은 쿼리가 있었다. 이 쿼리는 Redash 클라이언트와 연결이 끊긴 뒤에도 계속 보였고, wait_event 는 io:BufFileWrite 였으며, backend_xmin 이 VACUUM 로그의 oldest xmin 과 같은 1226078887 이었다. 나중에 보니 이 쿼리는 Performance Insights 에도 눈에 띄게 나타나 있었지만 당시에는 놓쳤다.
원인
원인 운영 실수
- 촉발
- 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 의 영향을 받기 쉬운 설계다.
출처
참고 문서
관련 사례
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0242primary 와 secondary PostgreSQL 데이터베이스를 모두 잃어 matrix.org homeserver 가 약 24시간 동안 멈췄다Matrix.org Foundation · 2025같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0531West US 의 Azure Database for MySQL Single Server 와 MariaDB 일부 서버로 가는 새 연결이 모두 거부됐다Microsoft Azure · 2024같은 원인 · 공통 데이터베이스
IL-0565쿠키런: 킹덤의 CockroachDB 클러스터가 노드 24대 중 16대의 파일 시스템을 잃고 멈춰 게임이 36시간 넘게 점검에 들어갔다Devsisters · 2022같은 원인 · 공통 데이터베이스
IL-0207로컬 개발 환경에서 돌린 작업이 운영 컨텍스트로 넘어가 Kubernetes 클러스터의 Consul 릴리스를 통째로 지웠고, Patroni 가 무너져 GitLab.com 전체가 5XX 오류를 냈다.GitLab · 2022같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0506UC Berkeley DataHub 의 JupyterHub sqlite 데이터베이스가 빈 데이터베이스로 덮어써져 로그인이 실패하고 홈 디렉터리가 비어 보이고 다른 사용자로 로그인되는 문제가 났다UC Berkeley · 2017같은 원인 · 공통 데이터베이스
IL-0218복제를 다시 맞추다 PostgreSQL 프라이머리의 데이터 디렉터리가 지워지고 백업이 작동하지 않아 GitLab.com 이 약 18시간 멈추고 데이터를 잃었다GitLab · 2017같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
IL-0319mysql2 를 다시 적재하려고 데이터베이스를 지우는 동안 mysql1 의 데이터베이스도 함께 지워졌다Oregon State University Open Source Lab · 2015같은 원인 · 공통 데이터베이스
같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0582
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0582/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.