# 备份仓库与保留策略

LLMS 索引： [llms.txt](/llms.txt)

---

仓库不是“放备份的目录”，而是一张有依赖、权限、密钥、过期与故障域的
历史图。一个对象存在，不代表它独立可恢复；一个对象过期，也可能让一串
后代失去意义。

## 21.3.1 全量、差异、增量与过期 {#item-21-3-1}

### 三种备份的依赖

以 pgBackRest 术语：

```text
full
  不依赖同仓库的更早 backup set

diff
  依赖最近 full，保存相对 full 的变化

incr
  依赖最近一次可作为父级的 backup，保存相对父级的变化
```

示意：

```text
F1
├── D1
│   ├── I1
│   └── I2
├── I3
└── D2
    └── I4

F2
└── I5
```

恢复 `I4` 需要 `F1 + D2 + I4`；恢复 `I2` 需要对应链。工具 catalog 负责
依赖，但 retention policy 必须与这张图一致。

### “增量”有多个层面

不要只看备份类型标签：

```text
logical incremental
  应用/CDC 按变化导出

file-level incremental
  只复制 mtime/size/checksum 判定变化的文件

block incremental
  只保存改变的数据块

PostgreSQL 18 native incremental
  WAL summaries + manifests + pg_combinebackup

pgBackRest incr
  pgBackRest repository dependency and restore semantics
```

同一个 `incr` 单词不保证恢复步骤相同。

### full 的价值

full 优点：

- 依赖图短；
- 恢复选择更直接；
- 祖先损坏影响范围小；
- 容易做离线复制或长期固定点。

代价：

- 读取和传输量大；
- checkpoint/I/O 影响；
- repository 增长；
- 大库窗口可能长。

但开启 block incremental、bundle 或 dedup 后，“full backup label”不一定
意味着仓库再次保存一份完整未去重字节。要区分：

```text
logical database size
backup delta read
repository delta written
repository total referenced size
```

本章 formal full：

```text
logical size             36,121,841 bytes
repository delta          4,539,288 bytes
```

这反映当前 pgBackRest block/bundle/compression 配置，不代表任何生产数据的
压缩率。

### differential 的恢复深度

diff 只依赖 full，所以常用于：

```text
weekly full
daily diff
intra-day incr
```

恢复最近日点可能只需 full + diff，而不是一长串 daily incremental。代价是
diff 随 full 之后的累计变化变大。

### incremental 的恢复深度

incr 降低单次备份量，但：

- chain 更深；
- 任一依赖损坏影响后代；
- restore 需要更多 metadata/object；
- catalog 与过期规则更关键；
- 小对象延迟可能抵消节省；
- 高频变化数据未必节省很多。

优化 backup window 不能以恢复路径不可测为代价。

### backup cadence 与 WAL replay

备份越旧，恢复到“现在”通常需要重放更多 WAL：

\[
T_{recovery}
\approx
T_{retrieve\ backup}
+T_{assemble}
+T_{replay\ WAL}
+T_{checkpoint}
\]

因此：

- 更频繁 base/diff 可缩短 replay；
- 更频繁备份增加 source/repository 工作；
- WAL 生成率、CPU、storage latency 影响 replay；
- parallel restore 不等于 parallel WAL replay 无限扩展。

通过生产规模演练测量，而不是从 backup duration 推算 restore duration。

### count 与 time retention

pgBackRest `repo1-retention-full-type` 决定
`repo1-retention-full` 的解释：

```text
count
  保留多少 full backup set

time
  以天为阈值保留 full
```

按 count=2 时，新 backup 要先成功，之后才可能 expire 最旧，因此瞬时可见
三份 full。按 time=20 时，需要存在至少一份达到对应年龄的 full 才形成过期
条件。不要把配置数字直接写成没有验证的“覆盖天数”。

### differential retention

`repo1-retention-diff` 按数量控制 diff。依赖被过期时，相应 incremental
也要一起处理。

政策样例：

```text
full: weekly, retain by time 35 days
diff: daily, retain 7
incr: every 6 hours
WAL: follow oldest retained recovery anchor
month-end logical archive: separate 13 months
```

这只是示例。真正参数要由场景、数据变化、仓库容量和 restore test 得出。

### WAL expiration

PITR 需要连续 WAL。pgBackRest 可随 backup expiration 删除不再被保留
backup 所需的 archive。更激进的 archive retention 能省空间，却可能缩短
PITR window。

原则：

```text
expire backup dependency graph first
derive which WAL is no longer useful
dry-run aggressive archive expiration
never delete WAL merely because it is "old"
```

一段 WAL 只有相对于某个可用 base 与目标才“有用”。孤立 WAL 不能独立恢复，
但错误删除 bridge segment 会破坏整个窗口。

### 自动过期的安全条件

在启用 expire 前，至少验证：

1. 目标 retention 用例已写成例子；
2. catalog 能画出 full/diff/incr dependency；
3. newest backup 成功后才触发过期；
4. repository 容量允许一次失败重试与过渡峰值；
5. legal hold 不会被普通规则删除；
6. off-site/immutable copy 的过期独立受控；
7. 定期恢复最旧目标；
8. dry-run 输出有人审阅；
9. 时钟、时区与对象 lifecycle policy 一致；
10. 删除权限与写入/恢复权限分离。

### 对象存储 lifecycle 的隐形删除

即使 pgBackRest retention 正确，bucket lifecycle 也可能：

- 提前删除 object/version；
- 转冷存储导致 RTO 激增；
- 删除 multipart/metadata；
- 与 legal hold 冲突；
- 在 repository catalog 不知情时改变可用性。

基础设施 lifecycle 必须成为同一份恢复设计的受控输入。

### 最旧目标演练

只恢复 latest backup 不能验证 retention：

```text
choose oldest required target
  -> identify required backup chain
      -> retrieve cold/off-site objects
          -> obtain historical key
              -> replay full WAL span
                  -> validate business marker
```

生产演练轮换：

```text
latest target
oldest target
random time target
target across timeline switch
target just before destructive transaction
target whose objects are in cold tier
```

## 21.3.2 校验、加密、不可变与异地副本 {#item-21-3-2}

### 四个不同属性

```text
integrity
  bytes 未损坏/未被替换

confidentiality
  未授权者不能读取

immutability
  在保留窗口内，包括高权限主体也不能轻易删除/改写

availability
  事故时对象、密钥、网络和权限仍可用
```

checksum 提供部分 integrity；加密提供 confidentiality；它们都不自动提供
immutability 或 availability。

### 校验的层次

| 层次 | 例子 | 能发现 | 不能发现 |
|---|---|---|---|
| transport | TLS/object ETag | 传输破坏的一部分 | 业务错误 |
| repository | pgBackRest checksum | object/file mismatch | key 丢失 |
| PostgreSQL page | data checksum | page corruption | 正确写入的错值 |
| backup manifest | `pg_verifybackup` | 文件/WAL manifest mismatch | target 选择错误 |
| restore start | recovery log | 链条可重放 | 业务不变量 |
| business | token/对账 | 目标事实差异 | 所有未来依赖 |

每层都必要，但结论边界不同。

### `pgbackrest check`

`check` 用于验证 stanza 配置与 archive path。典型：

```bash
sudo -iu postgres \
  pgbackrest --stanza=pg-test --log-level-console=info check
```

注意 pgBackRest 2.59.0 的 `check` 不接受 `--repo=1`；本章第一次 formal
尝试正因把 backup/info 的 repo selector 机械复制给 `check`，以 exit 31
在恢复前安全失败。这个失败保留了两个教学点：

```text
same tool != every command accepts same option
successful check != successful restore
```

正式成功运行修正命令后继续。

### 加密在哪里

可能的层次：

```text
client-side/repository encryption by backup tool
object storage server-side encryption
disk/volume encryption
transport TLS
application/column encryption
```

每层保护不同攻击面。pgBackRest repository cipher 需要 `cipher_pass`；如果
配置、备份与密码一起丢失，加密会非常成功地阻止所有人恢复。

### 密钥生命周期

备份 retention 往往比当前应用 key 生命周期长。密钥设计要回答：

- 谁能加密、谁能解密、谁能删除？
- rotation 后旧备份如何恢复？
- key version 与 backup label 如何关联？
- KMS/secret store 在区域事故中是否独立？
- break-glass 如何审批、审计和定期测试？
- 员工离职、账户冻结和组织恢复如何处理？
- legal deletion 是否通过 key destruction 实现，证据是什么？

不要把 cipher pass 复制到 evidence、工单或书中。本章 evidence 只记录
`cipher=aes-256-cbc`，不导出密码。

### 不可变不是只读 ACL

普通权限：

```text
backup writer can put
restore reader can get
operator can delete
```

若同一 credential 能写、覆盖、expire 与删除，攻击者拿到它就能破坏历史。

更强设计可能包括：

- object lock / WORM retention；
- versioning；
- 独立账户/项目；
- MFA-delete 或审批；
- writer 无 delete；
- lifecycle role 与 restore role 分离；
- retention policy 受治理；
- audit log 写入另一安全域；
- 定期从不可变副本恢复。

“S3-compatible”不等于实现并启用了这些特性。

### 不可变也会制造治理问题

设置错误的长期 object lock 会：

- 无法删除敏感数据；
- 产生不可控成本；
- 阻塞环境清理；
- 与法律删除义务冲突。

因此需要：

```text
retention class
legal hold process
minimum/maximum lock
authorized bypass
evidence and audit
test bucket before production
```

不可变是政策与控制面组合，不是一个布尔开关。

### 异地副本的独立性

“异地”至少检查：

```text
physical region
cloud/account/project
identity provider
KMS/key
network/control plane
operator role
automation blast radius
object lifecycle
billing/organization dependency
```

两 bucket 位于不同 region，但由同一高权限脚本执行 recursive delete，仍有
共同控制域。

### 3-2-1 只是启发式

常见经验：

```text
3 copies
2 media/system types
1 off-site
```

它提醒独立性，但不能替代场景合同。三份都被同一 credential 删除，数量没有
意义；一份真正不可变、离站且可恢复的副本，可能比十份同域复制更有价值。

### repository namespace

避免不同 cluster 冲突：

```text
repository
  -> stanza
      -> database/system-id lineage
          -> archive id
              -> timeline/WAL
```

不要把另一个 `initdb` 出来的 cluster 伪装成旧 cluster 继续向同一 archive
namespace 写入。pgBackRest stanza metadata 与 system ID 检查是保护层；
权限与路径隔离仍要做。

### 沙箱边界

本章仓库：

```text
one local MinIO
S3-compatible
AES-256-CBC repository cipher
shared laptop/hypervisor context
object lock not validated
cross-region copy not validated
independent credential domain not validated
```

所以正式结论带：

```text
EX21-REPOSITORY-NOT-IMMUTABLE
```

能够真实恢复，并不抹掉仓库共同故障。

## 21.3.3 容量预算、失败告警与责任人 {#item-21-3-3}

### 容量不是数据库大小乘份数

粗略预算：

\[
Capacity =
B_{full}
+\sum B_{diff}
+\sum B_{incr}
+WAL_{window}
+Metadata
+Versions
+SafetyMargin
\]

还受：

```text
database growth
change rate and full-page images
compression/dedup
block incremental
bundle
index churn
vacuum/rewrite
WAL generated by bulk load/DDL
object versioning
failed/in-progress backup
multipart residue
cold tier overhead
```

用历史指标和压力场景建模，不只用当前 `pg_database_size`。

### 两个增长率

```text
data growth rate
  determines future full/restore size

WAL generation rate
  determines archive bandwidth, RPO exposure and replay work
```

一个 1 TB 数据库每天只改 1%，与每天 rewrite 500 GB 的数据库，备份策略不同。

原生观测：

```sql
SELECT now(),
       pg_current_wal_lsn(),
       pg_walfile_name(pg_current_wal_lsn());

SELECT archived_count, failed_count,
       last_archived_wal, last_archived_time,
       last_failed_wal, last_failed_time
FROM pg_stat_archiver;
```

配合定时 LSN sample 估算 WAL rate。

### 带宽预算

需要同时考虑：

```text
source read throughput
source network egress
repository write/read throughput
archive sustained throughput
restore download throughput
WAL replay CPU/storage
shared production contention
```

backup 能在 4 小时窗口内完成，不代表事故时 restore 也能在 4 小时内完成：
方向、并发、cold retrieval 和 target infrastructure 都不同。

### 容量水位

至少建立：

```text
repository used / total
growth per day/week
forecast days to full
oldest/newest backup
oldest/newest recoverable target
WAL archive backlog
pg_wal filesystem free
backup duration and bytes
restore duration by representative size
object lifecycle transitions
```

阈值应给行动时间：

```text
warning when forecast leaves enough time to provision
critical before next backup/archive can exhaust capacity
```

固定 80%/90% 可能对高速增长系统太晚。

### 备份失败不是一个布尔告警

分类：

```text
scheduler did not start
backup started but failed
backup completed but required WAL missing
repository inaccessible
archive delayed
checksum/integrity failure
retention did not expire
retention expired too much
credential/key near expiry
restore drill failed
business validation failed
```

每种 owner 与紧急程度不同。

### freshness SLI

示意：

```text
age_of_latest_successful_base
age_of_last_independently_archived_wal
duration_of_archive_backlog
days_since_last_successful_restore
age_of_oldest_proven_restore_target
```

比“backup job success rate 99%”更接近 recoverability。

### 一次失败后先保护什么

当 backup job 失败：

1. 确认 archive 仍连续；
2. 确认 `pg_wal` 与 repository 容量；
3. 保存错误上下文与 catalog；
4. 判断现有 restore window 是否仍满足；
5. 修复依赖后重试；
6. 不要先 expire “腾空间”；
7. 若 RPO 已越线，升级 incident。

当 archive 失败：

1. 以磁盘耗尽预测为首要风险；
2. 保护未归档 WAL；
3. 恢复 repository path；
4. 确认最大 WAL 前进；
5. 安排隔离 restore 验证链条。

### RACI

一份实际 owner map：

| 能力 | Accountable | Responsible | Consulted |
|---|---|---|---|
| 业务 RPO/RTO | 业务 owner | SRE/平台 | DBA、安全 |
| PostgreSQL backup config | 平台 owner | DBA/平台 | SRE |
| repository capacity | 存储/平台 owner | 平台 | DBA |
| credential/key | 安全 owner | 安全/平台 | DBA |
| retention/legal hold | 数据治理 | 平台/法务 | 业务 |
| restore runbook | 平台 owner | DBA/SRE | 应用 |
| business validation | 业务 owner | 应用团队 | DBA |
| production cutover | incident/change owner | SRE/平台 | 全方 |

如果 backup 告警只有“DBA 群”负责，区域、密钥、应用验证很可能无人负责。

### 每日、每周、每季

示意节奏：

```text
continuous
  WAL/archive/capacity alert

daily
  latest backup, duration, bytes, catalog status

weekly
  dependency/retention review, sampled checksum

monthly
  automated isolated latest/selected restore

quarterly
  representative business validation and oldest target

annually or major change
  regional/break-glass/cutover exercise
```

频率按 tier 调整，但“从不恢复，只看 job”不属于任何成熟 tier。

### 可审计政策模板

```yaml
service: orders
tier: critical
scenarios:
  logical_error:
    rpo: named/audited transaction boundary
    rto_historical_query: 60m
  region_loss:
    rpo: 5m
    rto_read_write: 4h
backup:
  implementation: pgBackRest
  full: weekly
  diff: daily
  incr: 6h
  wal_archive: continuous
repository:
  primary: object-store-a
  immutable_copy: object-store-b
  failure_domain: separate account and region
  encryption_key: independent-dr-key
retention:
  operational_pitr: 35d
  monthly_logical: 13m
validation:
  latest_restore: monthly
  oldest_target: quarterly
  regional_cutover: annual
owners:
  platform: team-db
  business_validation: team-orders
  key: team-security
```

每个字段都应能找到 evidence，而不只是配置愿望。

## 小结

仓库的工程对象是：

```text
backup dependency graph
+ continuous WAL window
+ integrity
+ confidentiality
+ immutability
+ independent availability
+ capacity
+ ownership
```

下一节进入恢复动作本身：怎样从候选 backup 中选择真正能到达 target 的一份，
如何在启动前验证 lineage/WAL，以及为什么 PostgreSQL “一致”仍不足以交付。

---

[上一节：物理备份与 WAL 连续性](../02/) · [返回本章目录](../) · [下一节：恢复流程与验证](../04/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
