返回文章列表
postgresql

Vacuum 與維運

VACUUM 機制、Autovacuum 調優、Visibility Map、Transaction ID Wraparound 與 Freeze——PostgreSQL 最關鍵的維運知識

Aaron

為什麼需要 VACUUM

PostgreSQL 的 MVCC 把新舊版本都存在同一張表裡(見 MVCC 原理)。UPDATE 和 DELETE 留下的舊 tuple(dead tuple)不會自動消失,必須靠 VACUUM 回收。MySQL 不需要這個步驟,因為舊版本在 Undo Log 裡,Purge Thread 會自動清理。

VACUUM

上圖左側展示不 VACUUM 的後果:heap page 中大量 dead tuple 佔用空間但不可見,Sequential Scan 必須掃過這些垃圾,效能下降且檔案不斷膨脹。右側是 VACUUM 後的結果:dead tuple 被標記為可重用空間(加入 Free Space Map),新的 INSERT 可以重用這些位置。

下方比較兩種 VACUUM:一般 VACUUM 不阻塞讀寫、不縮小檔案(空間內部可重用),生產環境靠 autovacuum 自動執行;VACUUM FULL 會重建整張表、真正縮小檔案,但需要 AccessExclusiveLock 阻塞所有讀寫,生產環境盡量不用,改用 pg_repack 做 online rebuild。

VACUUM(一般)

VACUUM orders;
  1. 掃描表,找出所有 dead tuple(xmax 已 committed 且不再被任何活躍快照需要)
  2. 把 dead tuple 標記為可重用空間(加入 Free Space Map)
  3. 更新 Visibility Map(標記哪些 page 上全是 live tuple)
  4. 不回縮檔案大小——空間只是內部可重用,不歸還 OS

VACUUM 不阻塞讀寫(不加排他鎖),可以和正常 DML 併發執行。

VACUUM FULL

VACUUM FULL orders;

重建整張表——把 live tuple 複製到新檔案,刪掉舊檔案。真正回縮檔案大小,但:

  • 加排他鎖(AccessExclusiveLock),阻塞所有讀寫
  • 需要額外的磁碟空間存放新表
  • 執行時間和表大小成正比

生產環境盡量不用 VACUUM FULL。替代方案:pg_repack(online rebuild,不阻塞)。

Visibility Map

每個表有一個 Visibility Map(VM),每個 heap page 佔 2 bits:

  • all-visible:頁面上所有 tuple 對所有交易都可見
  • all-frozen:頁面上所有 tuple 都已 frozen(不需要再 vacuum)

用途:

  • Index-Only Scan:如果 VM 標記某 page 為 all-visible,可以直接從 index 拿資料不需要回 heap——這是 PostgreSQL 實現覆蓋索引的方式
  • VACUUM 加速:跳過 all-visible 的 page,只掃 dirty page

Autovacuum

手動 VACUUM 不實際,生產環境靠 autovacuum

觸發條件

dead tuple 數 > autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor × 表行數

預設值:threshold = 50、scale_factor = 0.2。意思是 dead tuple 超過「50 + 表總行數的 20%」就觸發。

對大表(1 億行),要死 2000 萬行才觸發——太晚了。大表應該調低 scale_factor:

ALTER TABLE huge_table SET (
    autovacuum_vacuum_scale_factor = 0.01,  -- 1%
    autovacuum_vacuum_threshold = 1000
);

關鍵參數

autovacuum = on                               # 永遠不要關
autovacuum_max_workers = 3                    # 同時跑幾個 worker
autovacuum_naptime = 1min                     # launcher 每分鐘檢查一次
autovacuum_vacuum_cost_limit = 200            # 每輪 vacuum 的 IO quota
autovacuum_vacuum_cost_delay = 2ms            # 用完 quota 後暫停時間

cost_limitcost_delay 控制 vacuum 的 IO 節流。預設值偏保守,寫入量大的系統建議調高 cost_limit(例如 1000-2000)讓 vacuum 跑快一點,否則 dead tuple 累積速度 > vacuum 清理速度,表會一直膨脹。

Transaction ID Wraparound

問題

PostgreSQL 的交易 ID(txid)是 32-bit 無符號整數,最大約 42 億。用完會 wraparound——txid 210 億突然變成「未來」的交易,所有資料對它來說都是「未來建立的」→ 全部不可見 → 整張表看起來像空的

解法:Freeze

VACUUM 除了清理 dead tuple,還負責 freeze 舊 tuple——把很久以前 committed 的 tuple 的 xmin 標記為一個特殊的 frozen txid(FrozenTransactionId = 2)。Frozen tuple 對任何交易都可見,不再需要 visibility check,也不受 wraparound 影響。

vacuum_freeze_min_age = 50000000         # tuple 存活超過 5 千萬個交易後 freeze
vacuum_freeze_table_age = 150000000      # 表的 age 超過 1.5 億時強制全表 vacuum
autovacuum_freeze_max_age = 200000000    # 超過 2 億時觸發 aggressive vacuum(不可跳過)

Anti-Wraparound VACUUM

當某張表的 relfrozenxid(最老的未 frozen txid)距離當前 txid 超過 autovacuum_freeze_max_age,autovacuum 會啟動 aggressive vacuum——掃描整張表(不管 Visibility Map),確保所有舊 tuple 都被 frozen。

如果 aggressive vacuum 也跟不上(比如表太大、IO 不夠),PostgreSQL 會在距離 wraparound 還剩 1000 萬個交易時關閉整個資料庫,拒絕任何新交易,強制你手動 vacuum。

-- 查看各表距離 wraparound 還有多遠
SELECT relname,
       age(relfrozenxid) AS xid_age,
       pg_size_pretty(pg_total_relation_size(oid)) AS total_size
FROM pg_class
WHERE relkind = 'r'
ORDER BY age(relfrozenxid) DESC
LIMIT 10;

這是 PostgreSQL 獨有的維運挑戰,MySQL 完全沒有這個問題。

監控

-- Dead tuple 數量
SELECT relname,
       n_live_tup,
       n_dead_tup,
       round(n_dead_tup::numeric / greatest(n_live_tup, 1) * 100, 1) AS dead_pct,
       last_autovacuum
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;

-- 表膨脹估算(需要 pgstattuple extension)
CREATE EXTENSION IF NOT EXISTS pgstattuple;
SELECT * FROM pgstattuple('orders');

-- Autovacuum 是否跑得動
SELECT relname, last_autovacuum, autovacuum_count
FROM pg_stat_user_tables
ORDER BY last_autovacuum DESC NULLS LAST;

總結

面向PostgreSQLMySQL (InnoDB)
舊版本存放同一張表(heap)Undo Log
清理機制VACUUM(必須)Purge Thread(自動)
表膨脹會(需要維運)不會
Crash 回滾不需要(標記 aborted)需要 Undo Log 逆向回復
覆蓋索引靠 Visibility Map直接在 Secondary Index 讀
Transaction ID32-bit,需要 freeze 防 wraparound隱藏欄位,沒有 wraparound 問題
維運複雜度高(autovacuum tuning 是必修課)