PostgreSQL 17 autovacuum, 이제 maintenance_work_mem 상한 신경 안 써도 된다

지난주에 팀에서 PG16 → PG17 업그레이드 검증하다가 알게 된 건데, 이거 모르는 분 꽤 많더라. 짧게 하나만 짚고 넘어간다.
핵심: 1GB 상한이 사라졌다
PG16까지는 autovacuum_work_mem(또는 fallback으로 maintenance_work_mem)이 아무리 커도 실질적으로 1GB 제한이 있었다. dead tuple ID를 저장하는 자료구조가 단순 배열이었고, TIDStore가 아니라 6바이트짜리 ItemPointerData 배열이라 메모리 크기와 상관없이 내부적으로 1GB(정확히는 MaxAllocSize)까지만 쓸 수 있었기 때문이다.
이게 뭐가 문제였냐면, 큰 테이블에서 dead tuple이 1GB 배열에 다 안 들어가는 순간 인덱스 vacuum 페이즈가 여러 번 반복된다. 인덱스가 10개 있는 1TB 테이블에서 이게 터지면 vacuum 한 사이클이 몇 시간씩 걸리고, 그동안 bloat는 계속 쌓인다.
PG17에서는 dead tuple 저장소를 adaptive radix tree 기반 TIDStore로 바꿨다. 결과적으로:
- 1GB 상한 없음 →
maintenance_work_mem설정한 만큼 다 쓴다 - 같은 dead tuple 수 기준으로 메모리 사용량이 대략 절반
- 인덱스 vacuum 페이즈가 여러 번 도는 상황이 거의 사라짐
우리 테스트 환경에서 재현해봤는데, 800GB짜리 append-heavy 테이블(인덱스 7개) 기준으로 PG16에서는 인덱스 페이즈가 9번 돌았던 게 PG17에서는 1번으로 끝났다. vacuum 총 소요시간 기준으로 대먽 1/6.
그래서 뭐를 바꾸면 되나
업그레이드하면서 이것만 확인하면 된다:
-- 큰 테이블
수십 GB 이상) 있는 인스턴스면 이 값을 넉넉히 올려도 된다
ALTER SYSTEM SET maintenance_work_mem = '4GB';
-- autovacuum에도 동일하게 적용하고 싶으면
ALTER SYSTEM SET autovacuum_work_mem = '4GB';
SELECT pg_reload_conf();
PG16까지는 4GB로 설정해도 어차피 1GB밖에 못 썼다. PG17부터는 진짜로 4GB 다 쓴다. RDS/Aurora처럼 파라미터 그룹으로 관리하는 환경이라면 파라미터 그룹 값을 다시 검토해봐야 한다 — "어차피 상한 있으니까 크게 잡아도 상관없다"고 방치해뒀던 값들이 이제 실제로 힙을 잡아먹는다.
한 가지 주의할 점. autovacuum_max_workers × autovacuum_work_mem 만큼 최악의 경우 메모리를 쓸 수 있다. worker 5개에 각각 4GB면 20GB. instance memory 대비 이걸 계산해두지 않으면 OOM 맞는다.
확인은 pg_stat_progress_vacuum으로
vacuum이 인덱스 페이즈를 몇 번 도는지는 이걸로 본다:
SELECT pid, relid::regclass, phase, index_vacuum_count, max_dead_tuple_bytes, dead_tuple_bytes
FROM pg_stat_progress_vacuum;
index_vacuum_count가 0이나 1에서 멈추면 정상. 2 이상 올라간다면 maintenance_work_mem을 더 올릴 여지가 있다. dead_tuple_bytes / max_dead_tuple_bytes 비율도 봐두면 튜닝에 도움된다.
그 외 PG17 autovacuum 관련 변화(적읕형 threshold, memory context 개선 등)는 각각 따로 다룰 만한 주제라 여기선 안 다뤘다. maintenance_work_mem 상한 제거 하나만으로도 파라미터 리뷰할 이유가 충분하다.
혹시 업그레이드 뒤에 autovacuum 동작이 예전이랑 다르게 느껴진다면 이것부터 확인해보시길.