Share Notes

chundev

View the Project on GitHub latteouka/share-notes

PostgreSQL 17 備份策略:TB 等級資料庫的 RPO 4 小時方案(不用 WAL Archiving)

日期:2026-03-21 環境:PostgreSQL 17.2 + CNPG (CloudNativePG) on K3s


TL;DR

pg_dump 每小時跑一次在資料量成長到 TB 等級後不可行(dump 本身可能就要跑超過一小時)。PostgreSQL 17 原生支援 增量備份(Incremental Backup),透過 summarize_wal 機制追蹤變更區塊,不是傳統 WAL archiving,空間消耗極小。搭配每 4 小時一次的增量備份,可同時滿足 RPO 4 小時需求和 TB 等級的可擴展性。


問題:pg_dump 的可擴展性瓶頸

資料量 pg_dump 耗時(估) 每小時跑一次?
10 GB ~2-5 分鐘 ✅ 可行
100 GB ~20-40 分鐘 ⚠️ 勉強
500 GB ~2-3 小時 ❌ 不可行
1 TB+ ~4-8 小時 ❌ 完全不可行

pg_dump 是邏輯備份 — 它會 SELECT 整個資料庫,轉成 SQL 語句。資料量愈大,耗時線性成長,且對 CPU/IO 有顯著壓力。當 dump 時間超過備份間隔,就形成備份永遠追不上的死局。


備份方案比較

方案 A:pg_dump 每小時(目前方案)

每小時: pg_dump → NFS
每日:   pg_basebackup (full) → NFS (GFS rotation)
優點 缺點
簡單好理解 TB 等級不可行
可跨版本還原 每次都是全量 dump
Schema 層級還原 對生產 DB 有 IO 壓力

適用規模:< 100 GB

方案 B:PG 17 原生增量備份(推薦)

每週:   pg_basebackup (full) → NFS
每 4h:  pg_basebackup --incremental → NFS
每日:   pg_dump → NFS (保留邏輯備份能力)
優點 缺點
TB 等級也只需幾分鐘 需要 PG 17+(已滿足)
只傳輸變更區塊 還原需 pg_combinebackup
不需要 WAL archiving 不支援 PITR
summarize_wal 極低開銷 增量鏈斷裂需重做 full

適用規模:任何大小,包含 TB+

方案 C:WAL Archiving + PITR

每日:   pg_basebackup (full) → NFS
持續:   archive_command 複製每個 WAL segment → NFS
優點 缺點
支援 PITR(精確到秒) WAL 空間消耗巨大(每日 50-100 GB+)
RPO 接近 0 NFS 頻寬壓力
業界標準做法 監控複雜度高

結論:對我們的場景來說空間消耗不划算,RPO 4 小時不需要秒級精確度。

方案 D:CNPG Volume Snapshots

透過 K8s CSI VolumeSnapshot API 做快照
優點 缺點
近乎即時的備份/還原 需要 CSI snapshot 支援
4.5 TB 還原只需 2 分鐘(EBS) local-path provisioner 不支援
原生 K8s 整合 需要企業級儲存後端

結論:我們用 local-path,不支援 CSI snapshot,暫不適用。


推薦方案:PG 17 增量備份

核心機制:summarize_wal ≠ WAL Archiving

這是最常被誤解的部分:

  WAL Archiving summarize_wal
做什麼 複製每一個 WAL segment(16MB/個) 產生摘要記錄哪些 block 被修改
空間消耗 高寫入量 = 每日數十到數百 GB 極小(每個摘要 < 1 MB)
用途 PITR(Point-in-Time Recovery) 增量備份(知道哪些 block 需要備份)
設定 archive_commandarchive_library summarize_wal = on(一個參數)

summarize_wal 的原理:如果一筆資料被 100 個 transaction 修改,WAL 會記錄 100 次變更,但 summarize_wal 只記錄「這個 block 被改過了」。增量備份只需要複製最終狀態,不需要重播所有變更。

備份流程

┌─────────────┐         ┌──────────────┐
│ 週日 01:00  │         │              │
│ Full Backup │────────→│              │
│ (完整備份)   │         │              │
└─────────────┘         │              │
                        │   NFS 備份    │
┌─────────────┐         │   儲存空間    │
│ 每 4 小時    │         │              │
│ Incremental │────────→│              │
│ (增量備份)   │         │              │
└─────────────┘         │              │
                        │              │
┌─────────────┐         │              │
│ 每日 01:00  │         │              │
│ pg_dump     │────────→│              │
│ (邏輯備份)   │         │              │
└─────────────┘         └──────────────┘

還原流程

# 1. 從 NFS 取得 full backup + 所有後續 incremental
# 2. 合併成完整備份
pg_combinebackup /backups/full /backups/incr1 /backups/incr2 ... -o /restore/combined

# 3. 用合併後的備份啟動 PG
pg_ctl -D /restore/combined start

空間估算(1 TB 資料庫)

備份類型 每次大小(估) 頻率 每週總量
Full backup ~1 TB 每週 1 TB
Incremental ~50-200 GB(看變更量) 每 4h = 42 次/週 2-8 TB
pg_dump ~300-500 GB 每日 2-3.5 TB

GFS 保留策略

設定方式

# postgresql.conf (透過 CNPG Cluster spec)
postgresql:
  parameters:
    summarize_wal: "on"
# Full backup (CronJob, 每週日)
pg_basebackup -D /backups/full/$(date +%Y%m%d) \
  --checkpoint=fast --wal-method=none --manifest-checksums=SHA256

# Incremental backup (CronJob, 每 4 小時)
pg_basebackup -D /backups/incr/$(date +%Y%m%d_%H%M) \
  --incremental=/backups/full/LATEST/backup_manifest \
  --checkpoint=fast --wal-method=none

RPO / RTO 分析

RPO(最大資料遺失量)

方案 RPO 計算方式
pg_dump 每小時 ~1 小時 最多遺失上一次 dump 後的變更
增量備份每 4 小時 ~4 小時 最多遺失上一次增量後的變更
WAL archiving ~0(即時) 持續歸檔,可還原到任意時間點
HA Replica 0(但不是備份) Streaming replication 即時同步

RTO(恢復時間目標)

還原情境 pg_dump 增量備份 Volume Snapshot
10 GB ~5 分鐘 ~2 分鐘 ~10 秒
100 GB ~30 分鐘 ~10 分鐘 ~30 秒
1 TB ~4-8 小時 ~30-60 分鐘 ~2 分鐘

增量備份的 RTO 比 pg_dump 快很多,因為它是物理備份(直接複製 data files),不需要重播 SQL。


遷移路徑

Phase 1:啟用 summarize_wal + 增量備份 CronJob

# CNPG Cluster spec 加入
spec:
  postgresql:
    parameters:
      summarize_wal: "on"

建立新的 CronJob:每 4 小時執行 pg_basebackup --incremental

Phase 2:降低 pg_dump 頻率

pg_dump 從每小時改為每日一次,保留作為邏輯備份(跨版本遷移、單表還原用)。

Phase 3:監控與驗證


實測數據(2026-03-23 更新)

已按 Phase 1-3 部署完成,以下是實際環境的數據(約 150 張表、23K 筆訊息):

備份類型 實際大小 耗時 說明
Full backup (pg_basebackup) 23 MB ~10s tar + gzip
增量備份 (4h 間隔) 556 KB ~3s 只有 full 的 2.4%
pg_dump 8.2 MB ~2s SQL + gzip

Standby 的 WAL Timeline 陷阱

增量備份從 Standby(Replica)執行時會失敗:

pg_basebackup: error: manifest requires WAL from final timeline 2
ending at 0/65000000, but this backup starts at 0/64000028
HINT: This can happen for incremental backups on a standby
if there was little activity since the previous backup.

解法:Full 和 Incremental 都改從 Primary 執行。 增量只有 556 KB,對 Primary 負擔可忽略。詳見 PG 17 增量備份在 Standby 上的 WAL Timeline 陷阱

Full backup 和增量備份完成後,用 symlink 指向最新的 manifest:

# Full backup 結束時
ln -sf /backups/full-$(date +%Y%m%d)/backup_manifest /backups/latest-manifest

# 增量備份也更新(形成鏈式增量)
ln -sf /backups/incr-$(date +%Y%m%d_%H%M)/backup_manifest /backups/latest-manifest

增量備份指令直接用 symlink:

pg_basebackup --incremental=/backups/latest-manifest ...

學到的事


參考資料