[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
---
|
||||
id: db-vacuum-autovacuum
|
||||
title: Vacuum / Autovacuum — Bloat / Wraparound 방지
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [database, postgres, vacuum, vibe-coding]
|
||||
tech_stack: { language: "Postgres", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [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
|
||||
→ 자주 작게 하라.
|
||||
```
|
||||
|
||||
```sql
|
||||
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
|
||||
```sql
|
||||
VACUUM (ANALYZE, VERBOSE) orders;
|
||||
-- Bloat 정리 + 통계 + log
|
||||
|
||||
-- Concurrent (lock 적음)
|
||||
VACUUM (ANALYZE) orders; -- 이미 concurrent default
|
||||
|
||||
-- Full (lock 큼 — 잘 안 씀)
|
||||
VACUUM FULL orders; -- 테이블 lock + rewrite
|
||||
```
|
||||
|
||||
### Bloat 측정
|
||||
```sql
|
||||
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)
|
||||
```sql
|
||||
CREATE EXTENSION pgstattuple;
|
||||
SELECT * FROM pgstattuple('orders');
|
||||
-- tuple_count, dead_tuple_count, free_space
|
||||
```
|
||||
|
||||
### Index bloat
|
||||
```sql
|
||||
CREATE EXTENSION pgstattuple;
|
||||
SELECT * FROM pgstatindex('orders_pkey');
|
||||
-- index_size, leaf_fragmentation
|
||||
```
|
||||
|
||||
→ Index bloat → REINDEX.
|
||||
|
||||
### REINDEX (CONCURRENTLY)
|
||||
```sql
|
||||
REINDEX INDEX CONCURRENTLY orders_pkey;
|
||||
-- lock 없이 새 인덱스 빌드 + swap
|
||||
```
|
||||
|
||||
### pg_repack (extension)
|
||||
```bash
|
||||
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 일 때 추적 필요.
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 가장 오래된 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.
|
||||
|
||||
```sql
|
||||
-- 명시 freeze
|
||||
VACUUM FREEZE orders;
|
||||
```
|
||||
|
||||
### Long-running transaction = autovacuum 차단
|
||||
```sql
|
||||
-- 오래된 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 차단.
|
||||
|
||||
```ts
|
||||
// App 에서 transaction 짧게
|
||||
async function work() {
|
||||
await db.transaction(async (tx) => {
|
||||
// ❌ 안 — fetch 외부 API
|
||||
// const data = await fetch(...);
|
||||
|
||||
// 짧게 — write 만
|
||||
await tx.insert(...);
|
||||
});
|
||||
// 외부 호출 후 commit
|
||||
}
|
||||
```
|
||||
|
||||
### Maintenance window
|
||||
```sql
|
||||
-- Off-peak 시간 더 적극 vacuum
|
||||
ALTER TABLE big_table SET (
|
||||
autovacuum_vacuum_cost_delay = 0 -- 빠르게
|
||||
);
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
```sql
|
||||
-- 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)
|
||||
```sql
|
||||
-- Index 컬럼 안 변경 + page 안 free space 있음 → HOT update
|
||||
-- → bloat 적음 + 빠름
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 측정
|
||||
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 높음 = 좋음.
|
||||
|
||||
```sql
|
||||
-- Fillfactor (page 안 free space 남기기)
|
||||
ALTER TABLE orders SET (fillfactor = 80); -- 20% 비움
|
||||
```
|
||||
|
||||
### Alarm
|
||||
```yaml
|
||||
- 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 항상.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[DB_Postgres_EXPLAIN]]
|
||||
- [[DB_Query_Optimization]]
|
||||
- [[DB_Lock_Analysis]]
|
||||
Reference in New Issue
Block a user