chundev
日期:2026-03-21 環境:PostgreSQL 17.2 + CNPG (CloudNativePG) on K3s
pg_dump 每小時跑一次在資料量成長到 TB 等級後不可行(dump 本身可能就要跑超過一小時)。PostgreSQL 17 原生支援 增量備份(Incremental Backup),透過 summarize_wal 機制追蹤變更區塊,不是傳統 WAL archiving,空間消耗極小。搭配每 4 小時一次的增量備份,可同時滿足 RPO 4 小時需求和 TB 等級的可擴展性。
| 資料量 | 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 時間超過備份間隔,就形成備份永遠追不上的死局。
每小時: pg_dump → NFS
每日: pg_basebackup (full) → NFS (GFS rotation)
| 優點 | 缺點 |
|---|---|
| 簡單好理解 | TB 等級不可行 |
| 可跨版本還原 | 每次都是全量 dump |
| Schema 層級還原 | 對生產 DB 有 IO 壓力 |
適用規模:< 100 GB
每週: pg_basebackup (full) → NFS
每 4h: pg_basebackup --incremental → NFS
每日: pg_dump → NFS (保留邏輯備份能力)
| 優點 | 缺點 |
|---|---|
| TB 等級也只需幾分鐘 | 需要 PG 17+(已滿足) |
| 只傳輸變更區塊 | 還原需 pg_combinebackup |
| 不需要 WAL archiving | 不支援 PITR |
summarize_wal 極低開銷 |
增量鏈斷裂需重做 full |
適用規模:任何大小,包含 TB+
每日: pg_basebackup (full) → NFS
持續: archive_command 複製每個 WAL segment → NFS
| 優點 | 缺點 |
|---|---|
| 支援 PITR(精確到秒) | WAL 空間消耗巨大(每日 50-100 GB+) |
| RPO 接近 0 | NFS 頻寬壓力 |
| 業界標準做法 | 監控複雜度高 |
結論:對我們的場景來說空間消耗不划算,RPO 4 小時不需要秒級精確度。
透過 K8s CSI VolumeSnapshot API 做快照
| 優點 | 缺點 |
|---|---|
| 近乎即時的備份/還原 | 需要 CSI snapshot 支援 |
| 4.5 TB 還原只需 2 分鐘(EBS) | local-path provisioner 不支援 |
| 原生 K8s 整合 | 需要企業級儲存後端 |
結論:我們用 local-path,不支援 CSI snapshot,暫不適用。
這是最常被誤解的部分:
| WAL Archiving | summarize_wal | |
|---|---|---|
| 做什麼 | 複製每一個 WAL segment(16MB/個) | 產生摘要記錄哪些 block 被修改 |
| 空間消耗 | 高寫入量 = 每日數十到數百 GB | 極小(每個摘要 < 1 MB) |
| 用途 | PITR(Point-in-Time Recovery) | 增量備份(知道哪些 block 需要備份) |
| 設定 | archive_command 或 archive_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
| 備份類型 | 每次大小(估) | 頻率 | 每週總量 |
|---|---|---|---|
| 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 | 計算方式 |
|---|---|---|
| pg_dump 每小時 | ~1 小時 | 最多遺失上一次 dump 後的變更 |
| 增量備份每 4 小時 | ~4 小時 | 最多遺失上一次增量後的變更 |
| WAL archiving | ~0(即時) | 持續歸檔,可還原到任意時間點 |
| HA Replica | 0(但不是備份) | Streaming replication 即時同步 |
| 還原情境 | 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。
# CNPG Cluster spec 加入
spec:
postgresql:
parameters:
summarize_wal: "on"
建立新的 CronJob:每 4 小時執行 pg_basebackup --incremental。
pg_dump 從每小時改為每日一次,保留作為邏輯備份(跨版本遷移、單表還原用)。
已按 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(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 ...