# 用 pgBackRest 交付备份策略

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

---

Pigsty 负责把 PostgreSQL、pgBackRest、仓库、归档、调度、日志和监控组合成
可交付基线；pgBackRest 负责 backup/archive/restore 机制；PostgreSQL 仍然
定义 WAL、recovery、timeline 与业务数据语义。

三层不要混淆：

```text
PostgreSQL
  physical history and recovery semantics

pgBackRest
  repository, backup sets, archive transport, restore orchestration

Pigsty
  declarative delivery, scheduling, service integration and observability
```

## 21.5.1 仓库、策略、调度与凭据 {#item-21-5-1}

### 从 Pigsty 声明开始

当前 Pigsty 的核心入口包括：

```yaml
pgbackrest_enabled: true
pgbackrest_method: minio       # or local/custom
pgbackrest_repo:
  minio:
    type: s3
    s3_endpoint: sss.pigsty
    s3_region: us-east-1
    s3_bucket: pgsql
    path: /pgbackrest
    storage_port: 9000
    block: y
    bundle: y
    bundle_limit: 20MiB
    bundle_size: 128MiB
    cipher_type: aes-256-cbc
    retention_full_type: time
    retention_full: 14
```

这里只展示非 secret 结构。`s3_key_secret` 与 `cipher_pass` 必须来自受控
secret source，不能提交到公开仓库、截图或 evidence。

参数随 Pigsty 版本演进。使用时以目标 tag 的参数参考为准，不能拿当前网页
替历史安装保证。

### local 与 MinIO

Pigsty 文档给出的常见方式：

```text
local
  local POSIX repository, default path /pg/backup

minio
  S3-compatible repository, optional MinIO service

custom
  pgBackRest supports other repository backends/config
```

职责差异：

| 方式 | 优势 | 必须显式接受的风险 |
|---|---|---|
| local same host/storage | 简单、低延迟 | 主机/磁盘共同故障 |
| shared local/NFS | 集中 | mount/锁/网络/同域 |
| MinIO/S3-compatible | object API、独立部署可能性 | endpoint、credential、object semantics |
| cloud object store | 跨域与 durability 选择 | 账户/KMS/费用/egress/lifecycle |

`pgbackrest_method=minio` 不自动证明 MinIO 位于独立故障域。

### stanza

pgBackRest stanza 把一个 PostgreSQL cluster 与 repository metadata 关联：

```bash
pgbackrest --stanza=pg-test stanza-create
pgbackrest --stanza=pg-test check
pgbackrest --stanza=pg-test info
```

Pigsty 在启用备份组件时交付配置/stanza。current 文档给出：

```bash
./pgsql.yml -t pg_backup
```

用于对已存在 cluster 启用相关 subtask；真正执行前：

- 固定 Pigsty repo/tag；
- 使用正确 inventory 与 limit；
- 先 diff/plan 变更；
- 确认不会删除/重建现有 stanza；
- 保护 secret output。

### 删除 cluster 与 stanza

Pigsty 文档提示，移除 primary 时可能删除 pgBackRest stanza，可用相应参数保留
backup。删除 cluster 的动作与删除 backup 历史是两个授权：

```text
remove database service
  != authorize deleting recovery history
```

所有 `pgsql-rm.yml` / `pg_rm_backup` 动作都应进入 destructive review。本章
不执行。

### backup 命令

Pigsty 提供 `/pg/bin/pg-backup`：

```bash
pg-backup          # documented default/incremental behavior
pg-backup full
pg-backup diff
pg-backup incr
```

原生 pgBackRest：

```bash
sudo -iu postgres \
  pgbackrest --stanza=pg-test --repo=1 \
             --type=full --log-level-console=info backup
```

使用 wrapper 的好处是平台约定一致；使用原生命令的好处是 exact option
清晰。本章 formal 固定原生命令并把版本、label、duration 与 source hash
写入 evidence。

### 调度

Pigsty 用 `pg_crontab` 声明：

```yaml
pg_crontab:
  - '00 01 * * 1 /pg/bin/pg-backup full'
  - '00 01 * * 2-7 /pg/bin/pg-backup'
```

本章沙箱实际观察：

```text
Monday 01:00 full
other days 01:00 default backup
```

调度要回答：

- 时区；
- primary role 切换后谁执行；
- concurrent/overlap lock；
- missed schedule；
- retry/backoff；
- load window；
- full/diff/incr policy；
- output/log rotation；
- alert source。

cron entry 存在不是 job 成功证据。

### primary 与 role change

HA cluster 中 backup job 不应因为原 primary 降为 replica 就盲目继续。平台
wrapper/配置应识别角色与 pgBackRest topology。演练切换后检查：

```text
which node owns cron
which node is repository stanza's database endpoint
archive push source
backup-from-standby policy
```

第 20 章切回原基线后，本章在 `pg-test-1` 执行 formal full。

### async archive 与 spool

pgBackRest 可用 async archive push/get：

```text
PostgreSQL archive command
  -> pgBackRest spool queue
      -> repository worker
```

好处是降低 postmaster archive command 的同步等待；代价是多一层队列状态。

观察：

```text
spool path
archive-push async log
queue size/age
repository max WAL
pg_stat_archiver
pg_wal free
```

验证 restore 时应使用独立 spool path，避免测试实例与 live archive worker
共享状态。本章：

```text
/data/pg36-ch21-restore/<run>/spool
```

### 凭据

典型 secret：

```text
S3 access key/secret
repository cipher pass
repository TLS client material
remote host key
database backup/replication credential
KMS token
```

配置文件最低要求：

- owner/group 与 mode；
- 不被普通 exporter/log collector 读取；
- command line/log 自动 redaction 验证；
- rotation；
- break-glass restore；
- 不与 source destruction role 共用；
- 不进入 `pgbackrest info` 的公开 projection。

本章 remote pgBackRest console 会把 secret 显示为 `<redacted>`，但 formal
evidence 根本不保存原始 console，只保存结构化无 secret 结果。

### 仓库 TLS 与 CA

S3-compatible endpoint 应验证：

```text
TLS enabled
expected CA
hostname/SAN
certificate rotation
clock
no silent downgrade
```

本章沙箱配置有 repository CA path，但未把完整 transport security 作为
生产验收；第 23 章继续。

### 声明与有效状态

三份证据：

```text
inventory declaration
generated /etc/pgbackrest config
effective command/catalog behavior
```

不能只看其中一个。生成配置可能漂移，声明可能未应用，运行命令也可能被环境
变量或额外 conf.d 覆盖。

安全投影应保留：

```text
stanza
repo type/path class
endpoint class
cipher type
retention type/value
block/bundle
archive async
spool path
```

去掉所有 secret value。

## 21.5.2 备份状态、归档状态与容量观察 {#item-21-5-2}

### `pgbackrest info`

人读：

```bash
sudo -iu postgres \
  pgbackrest --stanza=pg-test --repo=1 info
```

机器读：

```bash
sudo -iu postgres \
  pgbackrest --stanza=pg-test --repo=1 --output=json info
```

关注：

```text
stanza status code/message
backup/restore locks
repository id/cipher
database version and repository key
archive id/min/max
backup label/type/error
timestamp start/stop
archive start/stop
logical and repository bytes
prior/reference dependency
```

JSON 仍可能含 raw system ID；公开 evidence 应转成 equality relation。

### label 解读

示例：

```text
20260729-201041F
```

常见 suffix：

```text
F full
D differential
I incremental
```

不要只解析名字推断成功；同时看 catalog `error=false`、status、archive 和
实际恢复。

### `check`

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

它能触发/检查 archive path 与配置。不同 command 的 options 不完全相同：

```text
info/backup/restore accept --repo=1
check in pgBackRest 2.59.0 rejects --repo=1
```

把一串“通用参数”复制给所有命令是危险习惯。

### PostgreSQL 原生交叉验证

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

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

对照：

```text
PostgreSQL last archived
pgBackRest archive max
current WAL
spool queue
```

不同 observation 可能短暂错开，要用时间戳和 queue 解释。

### 累计失败数

本章沙箱：

```text
archived_count > 0
failed_count = 21
last successful time > last failed time
```

这说明过去有失败，当前最近归档成功。正确告警不是：

```text
failed_count > 0 forever critical
```

而是：

```text
failure counter increased recently
and/or last failure later than last success
and/or archive maximum stops advancing while WAL advances
and/or spool/backlog/free-space breaches
```

### backup freshness

计算：

```text
now - latest successful backup stop
```

还要按类型：

```text
latest full age
latest diff age
latest any backup age
oldest retained full
```

一份最新 incremental 可能依赖过旧 full；只看 latest label 隐藏 chain 风险。

### archive freshness

低流量时：

```text
last archive time old
current WAL same segment
```

不一定 backlog。高流量时：

```text
last archive time recent
but repository is many segments behind
```

仍可能越过 RPO。结合 rate 和 segment distance。

### 容量

源端：

```bash
df -h /pg/data /pg
du -sh /pg/data/pg_wal
```

repository：

```text
used bytes
object count
growth forecast
version/lock overhead
cold tier
retention expiry
```

restore target：

```bash
df -h /data
df -i /data
```

本章 `.13` 的 `/data` 有充足空间，正式 restore 只有约 36 MB；这不能证明
生产大库空间规划。

### 日志

Pigsty 常见 pgBackRest log path：

```text
/pg/log/pgbackrest/
```

分类：

```text
backup
expire
archive-push-async
archive-get-async
restore
```

日志要集中，但 redaction 后再进入平台。错误排查保留 `exec-id`、command
version、stanza 与 phase。

### dashboard 与 alert

Pigsty 提供 PGSQL PITR 等 dashboard/monitoring 入口。图表用于趋势与定位，
机器验收仍回到：

- PostgreSQL catalog；
- pgBackRest JSON；
- repository/storage fact；
- real restore evidence。

看板绿不等于 restore proof。

### 观察矩阵

| 信号 | warning | critical/action |
|---|---|---|
| latest full age | 接近 policy | 越过恢复合同 |
| archive max lag | 上升 | RPO 越线/磁盘风险 |
| `pg_wal` free | forecast shrink | 可能 PANIC |
| repository free | forecast shrink | 下一备份不可完成 |
| backup duration | trend regression | window/RTO risk |
| failed count delta | new failure | inspect/retry |
| restore drill age | 接近周期 | recoverability unproven |
| key expiry | rotation window | future restore blocked |

### 本章 evidence capture

[`capture.py`](/labs/ch21/capture.py) 默认只读：

```bash
PG36_EVIDENCE_DIR=/new/path \
  static/labs/ch21/task.sh capture
```

它采集：

```text
Patroni topology
PostgreSQL archive/current WAL/settings
sanitized pgBackRest catalog
no raw system ID
no credential
```

它明确输出：

```text
recoverability = not-proven-by-capture
```

因为 capture 没有执行 restore。

## 21.5.3 在隔离目标而不是原集群上恢复 {#item-21-5-3}

### Pigsty 的恢复入口

当前 Pigsty 文档提供：

```text
manual pg-pitr prompt/script path
pgsql-pitr.yml playbook path
```

文档说明，熟悉配置时可使用自动 playbook，否则建议逐步手工方式。其本质
原因是恢复 target、source 与 destructive action 需要人理解，不应让
automation 隐藏。

示意：

```bash
./pgsql-pitr.yml \
  -e '{"pg_pitr": {"time": "2026-07-29 20:10:44+00"}}'
```

这类 playbook 可能停止、清空、重建目标 instance。不要在 live cluster 上
为了练习运行。

### 当前文档 target forms

Pigsty `pg_pitr` 映射：

```yaml
pg_pitr: {}
pg_pitr: { time: "2026-07-29 20:10:44+00" }
pg_pitr: { lsn: "0/200002D0" }
pg_pitr: { xid: "250000", exclusive: true }
pg_pitr: { name: "before_release" }
pg_pitr: { type: "immediate" }
```

使用前核对目标 Pigsty tag 的 exact behavior，以及 `exclusive` 到
`recovery_target_inclusive` 的映射。

### 为什么本章不直接调用 destructive playbook

本章目标是验证备份，而不是替换服务。正式设计：

```text
source cluster remains running
restore under a brand-new path
no Patroni
no DCS
no TCP
no service endpoint
archive_mode=off
stop after proof
retain directory
```

Pigsty 提供配置、pgBackRest 与 repository；本章用底层 pgBackRest 对 fresh
path 恢复，以精确控制隔离边界。

### formal restore command

结构化示意：

```bash
sudo -iu postgres pgbackrest \
  --stanza=pg-test \
  --repo=1 \
  --set=20260729-201041F \
  --type=name \
  --target=pg36_ch21_<run>_keep \
  --target-action=promote \
  --target-timeline=latest \
  --archive-mode=off \
  --pg1-path=/data/pg36-ch21-restore/<run>/data \
  --spool-path=/data/pg36-ch21-restore/<run>/spool \
  --log-path=/data/pg36-ch21-restore/<run>/log \
  restore
```

不要直接复制 placeholder。正式 runner 生成并验证 exact run ID/path，拒绝
已存在目录。

### 启动覆盖

live Pigsty PostgreSQL config 含：

```text
hba_file=/pg/data/pg_hba.conf
ident_file=/pg/data/pg_ident.conf
log path under /pg/log/postgres
TLS path under /pg/cert
listen_addresses=0.0.0.0
port=5432
standby primary_conninfo/slot
```

若直接启动 restored config，会碰 live path、网络与复制配置。正式 runner
显式覆盖：

```conf
listen_addresses = ''
port = 55432
unix_socket_directories = '<private>/socket'
unix_socket_permissions = 0700
hba_file = '<private>/pg_hba.restore.conf'
ident_file = '<restored>/pg_ident.conf'
ssl = off
archive_mode = off
primary_conninfo = ''
primary_slot_name = ''
shared_preload_libraries = ''
logging_collector = off
cluster_name = 'pg36-ch21-restore'
```

同时携带 source recovery-critical maxima。

### same-host isolation 的边界

formal restore 放在 `pg-test-3`，而 `.13` 同时继续运行 live replica：

```text
live:
  /pg/data
  port 5432
  Patroni member pg-test-3

isolated:
  /data/pg36-ch21-restore/<run>/data
  Unix socket only
  internal port 55432
  no Patroni
```

这是 process/path/network isolation，不是 host/device/failure-domain
isolation，形成 `EX21-SHARED-RESTORE-HOST`。生产 restore 应使用独立主机。

### 启动完成不能只看 `pg_ctl`

```bash
pg_ctl -D "$restore/data" -w start
```

可能在只读 consistent state 返回。正式脚本随后：

```sql
SELECT pg_is_in_recovery(),
       current_setting('transaction_read_only'),
       current_setting('archive_mode'),
       current_setting('listen_addresses');
```

等待：

```text
recovery=false
transaction_read_only=false
archive_mode=off
listen_addresses=''
```

再做 rollback write。

### 停止并保留

验证后：

```bash
pg_ctl -D "$restore/data" -w -m fast stop
```

检查：

```text
postmaster.pid absent
Unix socket absent
TCP listener absent
restore directory present
live replica still streaming on 5432
```

保留目录支持审计，但不是长期服务。删除由单独 `reset:fixture` 授权。

### 防误操作设计

`task.sh`：

```text
capture|verify|review|all
  no mutation

drill:pitr
  requires exact target + four nonproduction guards + confirmation

reset:fixture
  separate destructive token + exact run ID
```

`all` 永远不调用 `drill:pitr`。这避免 CI/读者为了“跑全套检查”意外再做备份
或启动恢复。

### 版本迁移

迁移到新版本时，工时不只改命令：

```text
review PostgreSQL recovery setting changes
review pgBackRest command options/output schema
review Pigsty variables/playbooks
recreate sanitized baseline
run negative guards
perform fresh restore
compare readiness phases
update exceptions and evidence
```

[`migration-effort.json`](/labs/ch21/migration-effort.json) 列出 production
仍需补的 evidence，不把沙箱通过升级为生产。

## 小结

Pigsty 与 pgBackRest 的价值，是把可恢复性机制交付成一致、可观察、可自动化
的系统。正确使用方式是：

```text
declare with Pigsty
inspect with pgBackRest + PostgreSQL
prove with isolated restore
govern with explicit authority
```

下一节执行完整实验，并逐项解释 formal output、两个安全失败、十四个反例和
生产 gate。

---

[上一节：恢复流程与验证](../04/) · [返回本章目录](../) · [下一节：实战：完成一次隔离恢复演练](../06/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
