Incident Library

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 알림이 대량의 잡이 쌓였을 때 울렸다.

조치

후속 조치

교훈

que_jobs 와 전혀 관계없는 쿼리라도 같은 DB 인스턴스에서 오래 실행되면 que_jobs 의 dead tuples 를 치우지 못하게 막아 테이블이 계속 커지고, qg 의 디큐 성능 저하와 DB 부하 상승으로 이어진다. 글쓴이는 이것을 오래 걸리는 쿼리 때문에 VACUUM 이 효과를 내지 못하는 PostgreSQL 운영의 전형적인 경우로 보았다. 이런 상황에서 어디를 확인해야 하는지를 정해 두고 습관으로 만들어야 한다고 느꼈다. 현재 qg 가 쓰는 advisory lock 과 Recursive CTE 는 dead tuples 의 영향을 받기 쉬운 설계다.

출처

참고 문서

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 데이터베이스, PostgreSQL vacuum. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.