본문 바로가기
Q&A2026년 8월 13일2분 읽기

대량 삭제 후 테이블 용량이 안 줄고 count도 느려요, autovacuum 문제일까요?

v
vacuum_beomjun
조회 500댓글 1

작업 큐로 쓰는 job_queue 테이블이 INSERT 후 처리되면 DELETE 하는 구조인데, 실제 남아있는 행은 얼마 없는데도 테이블 디스크 사용량이 계속 커지고 SELECT count(*)도 갈수록 느려집니다.

SELECT relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables WHERE relname = 'job_queue';

 relname   | n_live_tup | n_dead_tup |   last_autovacuum
-----------+------------+------------+---------------------
 job_queue |      12000 |    4200000 | 2026-08-01 03:11

보시면 살아있는 행은 1만 2천인데 dead tuple이 420만이에요. last_autovacuum도 열흘 넘게 안 돌았고요. autovacuum이 알아서 청소해준다고 알고 있었는데 왜 안 도는 건지, 그리고 이미 부풀어버린 용량은 어떻게 줄이는지 궁금합니다.

댓글 1

김김영삼2개월 전
dead tuple 420만에 live 1만 2천이면 전형적인 테이블 bloat이고, autovacuum이 '안 도는' 게 아니라 '돌아도 못 지우는' 상황일 가능성이 높습니다.

Postgres는 어떤 행이 삭제돼도, 그 행이 아직 열려 있는 오래된 트랜잭션의 스냅샷에서 보일 수 있으면 물리적으로 못 지웁니다. 그래서 다음을 먼저 의심하세요. 첫째, 오래 살아있는 트랜잭션. pg_stat_activity에서 idle in transaction 이나 xact_start가 아주 옛날인 세션이 있는지 보세요. 이런 세션 하나가 xmin horizon을 붙잡고 있으면 그 이후 죽은 튜플을 통째로 못 치웁니다. 둘째, 안 쓰는 replication slot. pg_replication_slots에 active=false인 슬롯이 남아 있으면 마찬가지로 청소를 막습니다. 이 둘 중 하나가 원인인 경우가 정말 많아요.

또 하나, VACUUM은 죽은 공간을 '재사용 가능'으로 표시할 뿐 OS로 디스크를 돌려주진 않습니다. 그래서 이미 부푼 물리 용량을 줄이려면 VACUUM FULL(테이블 잠금, 서비스 중엔 부담) 이나 pg_repack(온라인으로 재작성) 을 써야 해요. count(*)가 느린 것도 결국 이 죽은 페이지까지 다 스캔하기 때문이고요.

근본 대책으로는, job_queue처럼 churn이 심한 테이블은 autovacuum_vacuum_scale_factor를 이 테이블만 낮게(예: 0.01) 잡아서 훨씬 자주 청소되게 하세요. ALTER TABLE job_queue SET (autovacuum_vacuum_scale_factor = 0.01) 식으로요. 더 나아가면 큐를 날짜/상태로 파티셔닝해서 DELETE 대신 파티션 DROP/TRUNCATE로 비우는 구조가 이런 bloat에서 자유롭습니다. DELETE를 대량으로 반복하는 큐 패턴 자체가 Postgres랑은 궁합이 안 좋아요.

우선 pg_stat_activity랑 replication slot부터 확인해 보시고, 뭐가 xmin을 잡고 있었는지 알려주시면 더 좁혀 드릴게요.
Ctrl+Enter로 등록