- Published on
PostgreSQL VACUUM 안될 때 autovacuum 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
운영에서 PostgreSQL VACUUM 문제는 대개 두 가지 형태로 나타납니다.
VACUUM을 돌렸는데도 디스크 사용량이 줄지 않고, 쿼리가 점점 느려진다autovacuum이 분명 켜져 있는데도dead tuple이 계속 쌓인다
이 글은 “왜 VACUUM이 안 되는 것처럼 보이는지”를 먼저 분해하고, 그 다음 “autovacuum이 실제로 따라오도록” 튜닝하는 방법을 단계적으로 다룹니다. 단순히 파라미터 몇 개 올리는 수준이 아니라, 테이블별 워크로드 특성에 맞춰 안전하게 조정하는 관점으로 설명합니다.
관련해서 bloat 자체를 진단하고 해결하는 흐름은 아래 글도 함께 보면 좋습니다.
1) VACUUM이 “안 된다”는 착시부터 정리
먼저, VACUUM은 “디스크를 줄이는 명령”이 아닙니다.
- 일반
VACUUM은 dead tuple을 재사용 가능하게 표시하고, 통계(ANALYZE)를 갱신하는 데 초점이 있습니다. - 파일 자체를 줄이는 것은
VACUUM FULL또는CLUSTER같은 테이블 재작성(rewrite) 계열입니다.
즉, VACUUM을 돌렸는데 pg_relation_size()가 그대로인 것은 정상일 수 있습니다. 문제는 “재사용 가능한 공간이 충분히 생겼는지”, “쿼리 플래너 통계가 최신인지”, “autovacuum이 적시에 처리 중인지”입니다.
다음 쿼리로 테이블별로 현재 상태를 먼저 확인합니다.
SELECT
schemaname,
relname,
n_live_tup,
n_dead_tup,
vacuum_count,
autovacuum_count,
analyze_count,
autoanalyze_count,
last_vacuum,
last_autovacuum,
last_analyze,
last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 30;
여기서 핵심은 n_dead_tup이 높은데 last_autovacuum이 오래됐거나, autovacuum_count가 거의 증가하지 않는 패턴입니다.
2) autovacuum이 못 도는 대표 원인 6가지
2.1 장기 트랜잭션이 dead tuple 정리를 막는다
PostgreSQL은 MVCC 특성상, 오래 열린 트랜잭션이 있으면 해당 스냅샷에서 보이는 튜플을 지울 수 없습니다. 이 경우 VACUUM이 돌아도 cleanup이 제한되어 dead tuple이 계속 남습니다.
확인은 다음처럼 합니다.
SELECT
pid,
usename,
state,
xact_start,
now() - xact_start AS xact_age,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start ASC
LIMIT 20;
xact_age가 비정상적으로 긴 세션이 있다면, 애플리케이션 커넥션 풀 설정, 배치 작업, 혹은 “트랜잭션 열고 대기” 패턴을 먼저 제거해야 합니다. autovacuum 파라미터를 올려도 장기 트랜잭션이 있으면 효과가 제한됩니다.
2.2 autovacuum worker 수가 부족하다
테이블이 많고 갱신이 활발하면, worker가 부족해 backlog가 생깁니다.
SHOW autovacuum_max_workers;
SHOW autovacuum_naptime;
autovacuum_max_workers가 너무 낮으면 동시에 처리할 수 있는 테이블 수가 제한됩니다.autovacuum_naptime이 길면 “다음 라운드”가 늦게 옵니다.
2.3 비용 제한(cost limit) 때문에 너무 느리게 돈다
기본 설정은 I/O 폭주를 막기 위해 autovacuum이 자주 쉬도록 설계되어 있습니다. 하지만 운영 워크로드가 크면 그 “배려” 때문에 따라오지 못합니다.
SHOW autovacuum_vacuum_cost_limit;
SHOW autovacuum_vacuum_cost_delay;
cost_limit이 낮거나cost_delay가 크면, vacuum이 I/O를 조금만 하고 자주 sleep합니다.
2.4 테이블별 임계치(threshold)가 workload에 안 맞는다
autovacuum 트리거는 대략 아래 조건으로 계산됩니다.
- vacuum 조건:
dead_tuples가vacuum_threshold + vacuum_scale_factor * reltuples이상
대형 테이블에서 vacuum_scale_factor가 기본값(예: 0.2)이면, “전체의 20%가 죽어야” vacuum이 시작됩니다. 업데이트가 잦은 테이블이라면 너무 늦습니다.
2.5 vacuum이 lock 때문에 기다리거나 스킵된다
일반 VACUUM은 강한 락을 오래 잡지 않지만, 특정 상황(예: DDL, 확장 작업, 인덱스 작업)과 충돌하면 지연될 수 있습니다.
2.6 통계가 낡아서 인덱스를 안 타고 더 느려진다
vacuum과 별개로 ANALYZE가 따라오지 못하면 플래너가 잘못된 계획을 고르고, 그 결과 대량 스캔이 늘어 I/O가 커져 vacuum이 더 불리해지는 악순환이 생깁니다.
인덱스가 갑자기 안 타는 현상까지 같이 보인다면 아래 글의 체크리스트도 같이 점검하세요.
3) “지금 autovacuum이 뭘 하고 있나”부터 관측하기
3.1 진행 중인 vacuum 확인
SELECT
pid,
datname,
relid::regclass AS relation,
phase,
heap_blks_scanned,
heap_blks_vacuumed,
index_vacuum_count,
max_dead_tuples,
num_dead_tuples
FROM pg_stat_progress_vacuum
ORDER BY pid;
여기서 phase가 오래 멈춰 있거나, 특정 테이블이 계속 반복해서 등장하면 “임계치가 너무 낮아 과도하게 도는지” 또는 “worker가 부족해 backlog가 큰지”를 구분해야 합니다.
3.2 vacuum 로그를 남겨서 ‘안 도는지’ ‘느린지’ 분리
운영에서 가장 추천하는 방법은 vacuum이 “안 도는 것”이 아니라 “너무 느려서 티가 안 나는 것”인지부터 로그로 증명하는 것입니다.
postgresql.conf 또는 파라미터 그룹에 아래를 고려합니다.
log_autovacuum_min_duration = '5s'
- 너무 낮게 잡으면 로그가 과도해질 수 있으니, 처음엔
5s또는10s정도로 시작해 관측 후 조정합니다.
4) 전역(global) autovacuum 튜닝: 안전한 방향성
전역 설정은 “기본값을 개선하되, 테이블별 오버라이드는 최소화”하는 전략이 안정적입니다.
아래는 흔히 사용하는 조정 방향입니다. 정확한 값은 디스크 성능, 동시 트래픽, 테이블 수에 따라 달라서 “원칙” 중심으로 보세요.
4.1 worker 수와 빈도
autovacuum_max_workers: 테이블 수가 많고 업데이트가 많으면 증가 고려autovacuum_naptime: vacuum 라운드 주기를 줄여 backlog 감소
예시(환경에 맞게 조정):
autovacuum_max_workers = 6
autovacuum_naptime = '10s'
4.2 cost 기반 튜닝
- 목표: vacuum이 너무 자주 sleep하지 않도록
cost_limit을 올리거나cost_delay를 낮춤
예시:
autovacuum_vacuum_cost_limit = 4000
autovacuum_vacuum_cost_delay = '2ms'
주의: 이 값은 I/O 사용량을 직접적으로 자극합니다. 스토리지가 느리거나, 피크 시간대에 I/O가 이미 빡빡하면 “시간대별로 다른 정책”이 필요할 수 있습니다.
4.3 analyze도 같이 따라오게
vacuum만으로는 플래너 품질이 보장되지 않습니다.
autovacuum_analyze_scale_factor = 0.05
autovacuum_analyze_threshold = 50
대형 테이블에서 분석이 늦으면, 인덱스가 있어도 플랜이 틀어지고 결과적으로 전체 시스템이 더 느려질 수 있습니다.
5) 핵심은 “테이블별” autovacuum 튜닝이다
운영 장애로 이어지는 bloat는 보통 일부 테이블(핫 테이블)에서 발생합니다. 전역 설정을 과격하게 바꾸기보다, 문제가 되는 테이블에만 ALTER TABLE ... SET으로 임계치를 낮추는 것이 효과적입니다.
5.1 업데이트가 매우 잦은 대형 테이블
예: 이벤트 로그 집계 테이블, 상태 갱신 테이블 등
ALTER TABLE public.events
SET (
autovacuum_vacuum_scale_factor = 0.01,
autovacuum_vacuum_threshold = 1000,
autovacuum_analyze_scale_factor = 0.02,
autovacuum_analyze_threshold = 500
);
의미:
scale_factor를 낮춰 “전체의 몇 퍼센트가 죽었을 때 vacuum할지” 기준을 앞당깁니다.threshold는 너무 작은 테이블에서 과도하게 도는 것을 막기 위한 최소치 역할을 합니다.
5.2 삭제가 몰리는 테이블(배치 purge)
삭제가 특정 시간에 몰리면 그 직후 dead tuple이 급증합니다. 이때 autovacuum이 늦게 오면 bloat가 눈덩이처럼 커집니다.
전략:
- purge 직후 수동
VACUUM (ANALYZE)를 배치에 포함 - 또는 해당 테이블의 scale factor를 더 공격적으로 낮춤
VACUUM (ANALYZE) public.sessions;
5.3 테이블별 cost 설정도 가능
특정 테이블만 vacuum을 더 빠르게 하고 싶다면 테이블 스토리지 파라미터로 오버라이드할 수 있습니다.
ALTER TABLE public.events
SET (
autovacuum_vacuum_cost_limit = 8000,
autovacuum_vacuum_cost_delay = 0
);
주의: cost_delay = 0은 강하게 밀어붙이는 설정입니다. 피크 시간대에 적용하면 쿼리 지연을 유발할 수 있으니, 먼저 로그와 I/O 지표로 영향도를 확인하세요.
6) VACUUM FULL을 쓰기 전에 알아야 할 것
VACUUM FULL은 디스크를 실제로 줄일 수 있지만, 테이블을 재작성하면서 강한 락을 잡아 운영 트래픽에 치명적일 수 있습니다.
대안 우선순위는 보통 아래가 안전합니다.
- autovacuum이 제때 돌도록 튜닝(대부분 여기서 해결)
- 문제 테이블만 maintenance window에
VACUUM (FULL)또는CLUSTER - 파티셔닝/아카이빙으로 “삭제를 줄이는 구조”로 변경
bloat가 이미 커졌다면 “튜닝만으로는 회복이 아니라 악화 방지”가 될 수 있습니다. 이 경우 실제 회복(파일 축소)은 재작성 계열 작업이 필요합니다. 이 부분은 아래 글의 bloat 진단 파트와 함께 보면 판단이 빨라집니다.
7) 실전 점검 체크리스트(운영에서 바로 쓰는 순서)
7.1 1단계: 장기 트랜잭션부터 제거
pg_stat_activity로xact_age긴 세션 확인- 애플리케이션에서 “트랜잭션 열린 채 대기” 패턴 제거
7.2 2단계: autovacuum이 실제로 돌고 있는지 관측
pg_stat_progress_vacuum확인log_autovacuum_min_duration로 느린 vacuum 로그 수집
7.3 3단계: backlog면 worker와 cost를 조정
autovacuum_max_workers증가autovacuum_vacuum_cost_limit상향 또는cost_delay하향
7.4 4단계: 핫 테이블만 scale factor를 낮춰 선제 vacuum
ALTER TABLE ... SET (autovacuum_vacuum_scale_factor = ...)- analyze도 같이 따라오게 설정
7.5 5단계: 이미 커진 bloat는 별도 플랜
- maintenance window 확보 후 재작성 작업 검토
- 장기적으로는 파티셔닝/보관 정책으로 삭제 자체를 줄이기
8) 마무리: “VACUUM이 안 된다”가 아니라 “조건이 안 맞는다”
대부분의 케이스에서 vacuum 자체가 고장 난 것이 아니라,
- 장기 트랜잭션이 cleanup을 막거나
- autovacuum 트리거가 너무 늦게 걸리거나
- cost 제한과 worker 부족으로 처리량이 밀리는
구조적 문제입니다.
먼저 관측(진행 상황, 로그)으로 “안 도는지 vs 느린지”를 분리하고, 전역 설정은 보수적으로, 문제 테이블은 과감하게 테이블별로 튜닝하는 방식이 운영 안정성과 효과를 동시에 가져갑니다.
필요하면 다음 단계로, bloat 진단 쿼리와 재작성 전략까지 이어서 정리한 글을 참고하세요.