AI Briefing

Postgres 큐를 건강하게 유지하기

·2026.04.18 08:00

핵심 내용

dead tuple와 MVCC horizon이 Postgres 큐를 서서히 망가뜨린다.

자세히 보기

Postgres에서 job queue를 돌릴 때의 핵심 문제는 dead tuple 누적과 그로 인한 table bloat, 그리고 점점 느려지는 index scan이다.

큐 테이블은 대부분의 행이 삽입-읽기-삭제로 짧게 순환하지만, 누적 처리량이 크기 때문에 vacuum이 조금만 밀려도 성능 저하가 빠르게 쌓인다. FOR UPDATE SKIP LOCKED를 쓰더라도 같은 B-tree 인덱스를 계속 훑는 구조는 바뀌지 않는다.

문제의 본질은 MVCC horizon이다. 오래 실행되는 트랜잭션이나 서로 겹치는 분석 쿼리가 하나라도 남아 있으면, 그 시점 이후의 dead tuple을 autovacuum이 정리하지 못한다. 즉, 큐 워커가 빠르더라도 같은 Postgres 인스턴스의 느린 워크로드가 vacuum을 막아 전체 큐 성능을 떨어뜨린다.

정리 메커니즘은 단순하다.

  • ctid, xmin, xmax 같은 행 메타데이터가 가시성을 결정한다.
  • dead tuple은 SELECT 결과에는 안 보이지만, sequential scan과 index scan 모두에서 추가 I/O를 만든다.
  • autovacuum은 autovacuum_naptime, autovacuum_vacuum_threshold, autovacuum_vacuum_scale_factor의 영향을 받는다.

문제는 “쿼리가 너무 오래 걸린다”가 아니라, 서로 다른 워크로드가 동시에 실행되면서 horizon을 계속 고정한다는 점이다. 단일 장기 트랜잭션뿐 아니라, 40초짜리 쿼리 여러 개가 20초 간격으로 엇갈려 실행되는 패턴도 vacuum을 막을 수 있다.

기존의 statement_timeout, idle_in_transaction_session_timeout, transaction_timeout는 개별 세션이나 단일 실행 시간만 겨냥하므로, 이런 동시성 기반 열화를 막기에는 부족하다.

PlanetScale의 Traffic Control은 여기에 쿼리 클래스별 리소스 제한을 걸 수 있는 수단으로 제시된다. SQLCommenter 태그 같은 메타데이터로 대상 쿼리를 분류하고, Maximum concurrent workers 같은 제어로 느린 분석 쿼리의 동시 실행 수를 제한해 autovacuum이 따라갈 시간을 확보한다. 차단된 쿼리는 영구 거부가 아니라 retry로 재시도되는 전제를 둔다.

재현 실험에서는 기존 recursive CTE 기반 큐와 SKIP LOCKED + batch 처리 버전을 비교했고, 두 방식 모두 오래되면 dead tuple 증가로 성능이 무너지는 곡선은 비슷했다. 다만 SKIP LOCKED와 batch는 초기 lock time과 큐 동작을 개선했지만, 근본 원인인 dead tuple 축적은 제거하지 못했다.

결론적으로, Postgres 큐는 여전히 강력하지만 같은 DB 안의 느린 분석 쿼리와 공존할 때 조용히 열화될 수 있다. 가장 중요한 대응은 큐를 더 똑똑하게 만드는 것보다, VACUUM이 따라갈 수 있도록 워크로드 동시성을 제한하는 것이다.

이 한국어 요약은 AI가 자동으로 만들었습니다. 원문의 주장과 맥락은 원문에서 확인해 주세요. 저작권은 원저작자에게 있습니다.

AI 처리 방식을 확인하거나, 요약 오류와 출처 표기 문제, 삭제 요청을 문의 · 건의로 알려주세요.