Files
2nd/10_Wiki/Topics/Coding/DB_Vacuum_Autovacuum.md
T
2026-05-09 21:08:02 +09:00

5.9 KiB
Raw Blame History

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
db-vacuum-autovacuum Vacuum / Autovacuum — Bloat / Wraparound 방지 Coding draft B conceptual 2026-05-09 2026-05-09
database
postgres
vacuum
vibe-coding
language applicable_to
Postgres
Backend
VACUUM
autovacuum
bloat
dead tuple
transaction wraparound
freeze

Vacuum / Autovacuum

Postgres MVCC = UPDATE/DELETE 가 dead tuple 생성. VACUUM 이 정리. Autovacuum 자동 — 그러나 큰 테이블 / write 많을 때 manual 필요. Bloat / wraparound 위험.

📖 핵심 개념

  • Dead tuple: 삭제 / 업데이트 된 row.
  • Bloat: dead tuple 누적 → 테이블 거대.
  • VACUUM: dead tuple 마킹 + 재사용.
  • VACUUM FULL: 테이블 rewrite (lock).
  • ANALYZE: 통계 업데이트.

💻 코드 패턴

Autovacuum 기본

default 활성. 각 테이블 별:
- 50 row + 20% 변경 → autovacuum
- 50 row + 10% 변경 → autoanalyze

큰 테이블 = autovacuum 안 따라옴

1억 row × 20% = 2천만 → autovacuum 한 번에 큰 cost
→ 자주 작게 하라.
ALTER TABLE orders SET (
  autovacuum_vacuum_scale_factor = 0.05,    -- 5% 마다 (default 20%)
  autovacuum_analyze_scale_factor = 0.02,
  autovacuum_vacuum_cost_limit = 2000,      -- I/O cost limit ↑
);

수동 VACUUM

VACUUM (ANALYZE, VERBOSE) orders;
-- Bloat 정리 + 통계 + log

-- Concurrent (lock 적음)
VACUUM (ANALYZE) orders;  -- 이미 concurrent default

-- Full (lock 큼 — 잘 안 씀)
VACUUM FULL orders;  -- 테이블 lock + rewrite

Bloat 측정

SELECT
  schemaname, relname,
  n_live_tup, n_dead_tup,
  round(100.0 * n_dead_tup / nullif(n_live_tup + n_dead_tup, 0), 2) AS dead_pct,
  last_vacuum, last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC LIMIT 20;

→ dead_pct > 20% = vacuum 필요.

pgstattuple (정확한 bloat)

CREATE EXTENSION pgstattuple;
SELECT * FROM pgstattuple('orders');
-- tuple_count, dead_tuple_count, free_space

Index bloat

CREATE EXTENSION pgstattuple;
SELECT * FROM pgstatindex('orders_pkey');
-- index_size, leaf_fragmentation

→ Index bloat → REINDEX.

REINDEX (CONCURRENTLY)

REINDEX INDEX CONCURRENTLY orders_pkey;
-- lock 없이 새 인덱스 빌드 + swap

pg_repack (extension)

pg_repack -d mydb -t orders
# Online table rewrite — bloat 제거 lock 없이

→ VACUUM FULL 의 zero-downtime 대안.

Transaction wraparound (위험)

Postgres = 32-bit transaction ID. 2B → wrap.
→ 모든 row 의 xmin 이 frozen 안 되면 — DB freeze.

Autovacuum 자동 freeze. but very busy DB 일 때 추적 필요.
-- 가장 오래된 frozen 까지의 거리
SELECT
  datname,
  age(datfrozenxid) AS xid_age,
  2147483647 - age(datfrozenxid) AS remaining
FROM pg_database
ORDER BY age(datfrozenxid) DESC;

→ 200M+ = 주의. 1.5B = 위험. 2B = freeze.

-- 명시 freeze
VACUUM FREEZE orders;

Long-running transaction = autovacuum 차단

-- 오래된 transaction
SELECT pid, usename, query, state, age(NOW(), query_start) AS age
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY query_start;

→ 30분+ = 의심. 2시간+ = autovacuum 차단.

// App 에서 transaction 짧게
async function work() {
  await db.transaction(async (tx) => {
    // ❌ 안 — fetch 외부 API
    // const data = await fetch(...);
    
    // 짧게 — write 만
    await tx.insert(...);
  });
  // 외부 호출 후 commit
}

Maintenance window

-- Off-peak 시간 더 적극 vacuum
ALTER TABLE big_table SET (
  autovacuum_vacuum_cost_delay = 0  -- 빠르게
);

Monitoring

-- Autovacuum 진행
SELECT pid, datname, query, query_start
FROM pg_stat_activity
WHERE query LIKE 'autovacuum:%';

-- 횟수
SELECT relname, n_tup_ins, n_tup_upd, n_tup_del,
       autovacuum_count, autoanalyze_count,
       last_autovacuum, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY n_tup_upd + n_tup_del DESC LIMIT 20;

Tunables

autovacuum_max_workers = 3       -- worker 수
autovacuum_naptime = 1min        -- check 주기
maintenance_work_mem = 256MB     -- vacuum 메모리

# 큰 cluster:
autovacuum_max_workers = 6
maintenance_work_mem = 2GB

HOT update (no index update)

-- Index 컬럼 안 변경 + page 안 free space 있음 → HOT update
-- → bloat 적음 + 빠름
-- 측정
SELECT relname, n_tup_upd, n_tup_hot_upd,
       round(100.0 * n_tup_hot_upd / nullif(n_tup_upd, 0), 2) AS hot_pct
FROM pg_stat_user_tables ORDER BY n_tup_upd DESC LIMIT 20;

→ hot_pct 높음 = 좋음.

-- Fillfactor (page 안 free space 남기기)
ALTER TABLE orders SET (fillfactor = 80);  -- 20% 비움

Alarm

- alert: HighDeadTuples
  expr: pg_stat_user_tables_n_dead_tup / pg_stat_user_tables_n_live_tup > 0.3
  for: 1h

- alert: TransactionWraparound
  expr: pg_database_xid_age > 1500000000
  for: 10m

🤔 의사결정 기준

상황 액션
Dead pct > 20% Manual VACUUM
Bloat 큼 pg_repack
Index bloat REINDEX CONCURRENTLY
Wraparound 임박 VACUUM FREEZE
Long transaction App fix — short tx
큰 write 테이블 scale_factor 낮게

안티패턴

  • VACUUM FULL prod: lock — table 못 사용. pg_repack.
  • Autovacuum 끄기: bloat 폭발.
  • Long transaction (30분+): autovacuum 차단.
  • 모든 table 같은 setting: 큰 vs 작은 다름.
  • Wraparound 모니터링 X: freeze 위험.
  • REINDEX 직접 prod: lock. CONCURRENTLY.
  • Fillfactor 100% prod: HOT update X — bloat.

🤖 LLM 활용 힌트

  • 큰 table = scale_factor 낮게 + cost limit 높게.
  • Long transaction = 적.
  • pg_repack 가 zero-downtime maintenance.
  • Wraparound + bloat alarm 항상.

🔗 관련 문서