PostgreSQL이 MVCC 때문에 쌓인 옛 버전(죽은 튜플)의 공간을 회수하는 작업. 보통 백그라운드의 autovacuum이 자동으로 돈다.
왜 필요한가 — MVCC의 뒤치다꺼리
PostgreSQL 의 UPDATE
- ① 옛 튜플을 '죽은 튜플(dead tuple)' 로 표시 ← 지우지 않는다
- ② 새 튜플을 힙에 추가
왜 안 지우나?
- 아직 옛 스냅샷을 보고 있는 트랜잭션이 있을 수 있다
죽은 튜플이 계속 쌓이면 테이블과 인덱스가 부푼다(bloat). 실제 데이터는 100MB인데 파일은 3GB가 되는 식이다. 읽어야 할 페이지가 늘어나므로 모든 쿼리가 느려진다.
가장 흔한 사고 — 긴 트랜잭션이 VACUUM을 막는다
오래 열린 트랜잭션 하나 (배치·안 닫은 커넥션·열어 둔 psql 세션)
↓
VACUUM: "그 트랜잭션이 아직 볼 수도 있는 튜플은 못 지운다"
↓
죽은 튜플이 계속 쌓인다 → 테이블·인덱스가 부푼다 → 전체가 느려진다
DELETE를 했는데 디스크가 안 줄어드는 증상의 정체가 이것이다. 확인은 이렇게 한다.
-- 가장 오래된 트랜잭션이 얼마나 열려 있나
SELECT pid, now() - xact_start AS age, state, query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY age DESC LIMIT 5;
-- 테이블별 죽은 튜플 수와 마지막 vacuum 시각
SELECT relname, n_live_tup, n_dead_tup, last_autovacuum
FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;
VACUUM과 VACUUM FULL은 다르다
-
VACUUM — 죽은 튜플 공간을 '재사용 가능' 하게 표시한다
- 파일 크기는 그대로. 서비스 중 실행 가능
-
VACUUM FULL — 테이블을 통째로 다시 써서 파일 크기를 줄인다
- ACCESS EXCLUSIVE 락 — 그동안 읽기조차 막힌다
운영 중에 VACUUM FULL을 무심코 돌리면 서비스가 멈춘다. 온라인으로 줄이려면 pg_repack 같은 도구를 쓴다.
InnoDB에도 같은 문제가 있다
이름만 다르다 — undo log를 정리하는 purge 스레드가 긴 트랜잭션에 막히면 undo 히스토리가 길어져 조회가 느려진다. 구현은 달라도 증상은 판박이다.
면접 함정
- ❌ "VACUUM이 디스크를 반환한다" → 일반 VACUUM은 재사용 표시만 한다. 파일 축소는
VACUUM FULL이나pg_repack이다. - ❌ "autovacuum이 알아서 하니 신경 안 써도 된다" → 긴 트랜잭션이나 대량 갱신 앞에서는 못 따라간다.
bloat가 얼마나 됐는지 재기
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstattuple('orders');
-- table_len 실제 파일 크기
-- dead_tuple_percent 죽은 튜플 비율 ← 20% 를 넘으면 손볼 때다
-- free_percent 재사용 가능한 빈 공간
autovacuum이 못 따라갈 때
-- 갱신이 잦은 테이블만 임계치를 낮춘다 (기본은 죽은 튜플 20%)
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.05, -- 5% 만 넘어도 돈다
autovacuum_vacuum_cost_delay = 0 -- 속도 제한을 풀어 빨리 끝낸다
);
큰 테이블일수록 기본값 20%가 너무 늦다 — 1억 행이면 2천만 행이 죽어야 비로소 vacuum이 시작된다. 그때는 이미 느려져 있고, vacuum 자체도 오래 걸린다.
서비스를 멈추지 않고 파일 크기 줄이기
# VACUUM FULL 은 ACCESS EXCLUSIVE 락 — 운영 중 금지에 가깝다
# pg_repack 은 새 테이블을 만들어 교체하므로 락이 짧다
pg_repack -t orders -d mydb
잊기 쉬운 또 하나 — 트랜잭션 ID 순환
SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC;
-- 20억에 가까워지면 DB 가 쓰기를 거부하고 강제 vacuum 에 들어간다
VACUUM은 공간 회수뿐 아니라 트랜잭션 ID를 동결(freeze) 하는 일도 한다. 이걸 방치하면 어느 날 DB가 통째로 읽기 전용이 된다.