데이터베이스 용어 사전
동시성 제어autovacuum · 죽은 튜플 · bloat

VACUUM

PostgreSQL이 MVCC로 생긴 죽은 튜플의 공간을 회수하는 작업. 긴 트랜잭션이 있으면 못 지워 테이블이 부푼다.

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가 통째로 읽기 전용이 된다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 동시성 제어 — 2PL·MVCC·락·데드락(PostgreSQL·MySQL)