21 未雨绸缪:备份体系与恢复演练
pgbackrest info 显示 status: ok,不等于数据可恢复;每天都生成备份,
不等于误删后能回到正确时刻;有三台流复制副本,更不等于有一份独立备份。
备份体系真正要交付的是一个可证伪的命题:
当某个已声明的损失场景发生时,团队能否找到一条完整、可信、权限可用的
恢复链,在隔离环境中把 PostgreSQL 带到预定边界,验证数据库与业务不变量,
再以受控方式交付服务?
这句话里没有“备份成功率”这个单一答案。它至少包含:
scenario
-> recovery point objective
-> base backup lineage
-> continuous WAL
-> repository and key availability
-> target selection
-> isolated restore
-> PostgreSQL consistency
-> business validation
-> controlled cutover
本章先从误删、介质丢失、区域故障和合规留存反推 RPO/RTO;再解释物理
基础备份、WAL、timeline 与归档链;随后把 full/diff/incr、过期、加密、
不可变和异地副本放进同一份仓库设计;最后用 Pigsty 与 pgBackRest
完成一次真实的命名恢复点演练。
本章不把成功说大:
fresh full backup 通过
named-point physical PITR 通过
base + keep / no discard 通过
isolated Unix-socket postmaster 通过
new timeline and same lineage 通过
source cluster remains healthy 通过
arbitrary-time recovery 未测试
missing WAL / lost key 未注入
immutable off-site repository 未证明
production-sized RTO 未证明
regional disaster recovery 未测试
production approval pending
本章目标
读完并完成实验后,你应当能够:
- 从损失场景与业务真相出发,而不是从“每天全备”出发设计恢复;
- 区分 RPO、RTO、恢复粒度、保留周期、历史版本数和法律留存;
- 说明逻辑备份、物理备份、存储快照、流复制与 CDC 各自能恢复什么;
- 解释基础备份为何必须配合一条连续 WAL 链;
- 从
backup_label、LSN、WAL 文件名与 timeline history 判断物理血缘;
- 设计幂等且不会覆盖不同内容的归档路径,并理解归档积压为何会填满
pg_wal;
- 正确比较全量、差异、增量的依赖、恢复复杂度与过期语义;
- 把加密密钥、不可变、独立凭据、异地副本与恢复权限纳入仓库合同;
- 区分“仓库可读”“文件恢复完成”“只读可用”“提升完成”“业务可用”;
- 正确选择 time、name、XID、LSN、inclusive/exclusive 与 timeline;
- 在 Pigsty 中声明、观察和操作 pgBackRest,而不把平台包装当成原理;
- 在不覆盖原集群的前提下完成一次可重放、可审计的隔离恢复;
- 输出测量口径、证据、反例、例外与生产准入差距;
- 知道成功恢复一次之后,下一次应该故意测试哪些失败路径。
前置与后续
前置:
后续:
- 第 22 章 服务接入、连接池与路由 处理恢复后如何
把客户端安全带到正确角色;
- 第 23 章深入身份、传输、凭据与密钥;
- 后续容量、监控、变更与事故章节会把恢复证据纳入生产治理;
- 区域级 DR 和真正的 destructive replacement 必须在独立授权的演练中
完成,不由本章沙箱命令暗中代替。
学习路径
business loss scenario
-> authoritative truth + tolerated loss
-> RPO / RTO / granularity / retention
-> logical vs physical vs snapshot vs replica
-> base backup + WAL continuity + timeline
-> repository dependency graph
-> select backup and target
-> restore into isolation
-> wait through promotion
-> database + business proof
-> gaps and next drill
这条路径故意不从复制 pgbackrest restore 命令开始。恢复命令是一个高风险
状态迁移;没有目标语义、血缘、WAL、隔离和验收标准时,命令执行得越顺利,
越可能迅速得到一个“能启动但不该交付”的数据库。
四层恢复证明
把“可恢复”拆成四层,能避免指标替代:
本章正式实验走到数据层,并用 rollback-only write probe 证明提升后可写;
它不切换生产路由,因此没有宣称服务层 cutover 通过。
正式实验拓扑
target pg36-l2-vagrant/pg-test
Pigsty v4.5.0
PostgreSQL 18.6
pgBackRest 2.59.0
source pg-test-1, live primary, timeline 7
repository S3-compatible MinIO, AES-256-CBC, one sandbox target
restore host pg-test-3
live member Patroni replica on 5432, unchanged
isolated copy fresh path + private Unix socket + port 55432
archive push off
recovery target named point
target action promote
target timeline latest
实验业务边界:
base committed before fresh full backup must exist
keep committed after backup, before target must exist
target named restore point
discard committed after target must not exist
正式观测:
backup label 20260729-201041F
backup command 2.086 s
pgBackRest check 0.598 s
logical backup bytes 36,121,841
repository delta bytes 4,539,288
restore copy 2.758 s
start -> first connection 0.963 s
first connection state recovery=true, read_only=true
start -> promoted and writable 1.319 s
read-only -> promoted 0.356 s
source timeline -> restored timeline 7 -> 8
system identifier relation matches source
source replica lag after drill 0 bytes
counterexamples rejected 14
这些时间是 36 MB 级合成沙箱的一次观测,不是生产 RTO。它们最有价值的
发现反而是:
pg_ctl -w start 返回时,实例可能刚进入 hot standby 的只读可用阶段,
recovery_target_action=promote 尚未完成。
所以正式脚本没有把“第一条 SELECT 成功”当成恢复完成,而是继续等待
pg_is_in_recovery() = false,再做一次回滚写入。
十项例外
沿用第 19 章六项:
EX19-SHARED-HYPERVISOR
EX19-SINGLE-ETCD
EX19-SINGLE-BACKUP-TARGET
EX19-VIRTUAL-STORAGE
EX19-INVENTORY-SECRETS
EX19-LAB-RESOURCE-FLOOR
本章新增四项:
EX21-SHARED-RESTORE-HOST
恢复进程、目录与网络隔离,但与 live replica 共用 guest/hypervisor;
不能声称主机、内核、设备和故障域隔离。
EX21-REPOSITORY-NOT-IMMUTABLE
只有本地单 MinIO;未证明 object lock、独立凭据或跨区域副本。
EX21-SMALL-SYNTHETIC-DATA
数据量很小;不能拿时间结果做生产容量规划。
EX21-NAMED-POINT-ONLY
只验证一个命名点,并主动切 WAL 后检查;不能代表任意时间点或最坏
归档间隙 RPO。
例外不是装饰性免责声明。每项都对应一个被禁止的推论,机器验收要求它们
完整保留。
本章目录
实验入口
动作语义:
capture / verify / review / all
L0 read-only
drill:pitr
guarded local sandbox mutation
insert markers + full backup + fresh isolated restore + stopped retention
reset:fixture
destructive and separate
delete exactly one reviewed run only
all 只重验已有证据,不会为了演示方便再做一份备份或再启动一次恢复。
权威资料
原理优先以当前 PostgreSQL 18 文档为准:
实现与平台入口:
版本相关命令在使用前应回到对应版本文档核对。本章 formal evidence 固定
在 Pigsty v4.5.0、PostgreSQL 18.6 与 pgBackRest 2.59.0;“当前文档入口”
不是“历史版本命令完全相同”的承诺。
本章最重要的判断
backup command succeeded != recoverable
catalog status ok != WAL chain complete for every target
replica healthy != independent backup
physical restore started != recovery complete
read-only query succeeded != promotion complete
PostgreSQL consistent != business truth correct
one fast sandbox restore != production RTO
encrypted repository != ransomware resistance
retained for 14 days != 14 days of arbitrary PITR
restore directory retained != service approved
真正的完成条件是:
declared scenario
+ selected truth boundary
+ complete lineage and WAL
+ isolated executable restore
+ engine and business proof
+ controlled service decision
+ explicit residual risk
下一节从第一项开始:先定义究竟要从什么损失中恢复。
上一章:狡兔三窟:高可用拓扑与容灾目标 · 返回下卷导读 · 下一章:四通八达:服务接入、连接池与路由 ·
查看全书目录 · 查看索引中心
21.1 从恢复场景设计备份
备份设计最容易犯的错误,是从一个漂亮的日历开始:
它回答了“工具何时运行”,却没有回答:
什么会丢?
丢了什么算业务损失?
要回到哪个边界?
由谁判断边界正确?
依赖也丢失时怎么办?
多快恢复到什么能力?
正确顺序是从场景与真相出发,再选择恢复机制、保留和调度。
21.1.1 误删、介质故障、区域故障与合规留存
先定义“损失”
数据库恢复不是把字节放回磁盘,而是重新建立被业务接受的事实。至少区分:
logical loss
数据库仍运行,但某些正确事实被删除、覆盖或错误变更
physical loss
数据文件、WAL、设备或整个集群不可用/不可信
site loss
数据库、控制面、接入层和本地备份一起不可用
historical obligation
必须证明某个历史版本可恢复、可查询或已按政策删除
不同损失的 truth source 不同。误删一行时,最可信的边界可能来自审计事件;
存储毁坏时,最可信的边界是仓库中最后完整基础备份与连续 WAL;区域故障时,
本地仓库本身不再是可用前提。
场景一:误删与逻辑破坏
典型事件:
DELETE FROM orders;
UPDATE account SET balance = 0;
DROP TABLE customer;
deploy buggy migration;
application writes wrong currency or tenant id;
主库、流复制副本和同步副本都会忠实重放这些操作。HA 可能保持服务在线,
却更快地把错误复制到每份在线副本。
误删恢复要先问:
- 错误事务的开始、提交和影响范围是什么?
- 目标是整个集群回退,还是只取回一张表/一批行?
- 目标时刻之后有哪些正确交易不能丢?
- 如何把旧事实与当前事实合并?
- 序列、外键、触发器、审计与外部系统如何一致?
常见安全路径不是“原地 PITR 回退生产”,而是:
restore historical cluster in isolation
-> validate target boundary
-> export affected logical set
-> compare with current production
-> reviewed merge or repair
原地回退会同时删除误操作之后的所有正确提交,通常扩大损失。
场景二:介质、文件系统或集群丢失
典型事件:
data volume lost
filesystem corruption
all HA members share failed storage
operator removes whole cluster
encryption layer/key unavailable
malware changes data and online copies
这时要恢复的是整个物理集群。所需链条通常为:
compatible PostgreSQL runtime
+ one usable base backup
+ every required WAL segment
+ timeline history
+ tablespace/path mapping
+ repository credential and encryption key
+ configuration and identity material
注意最后两项不一定在 PostgreSQL 物理备份内。官方连续归档文档明确提醒,
postgresql.conf、pg_hba.conf 与 pg_ident.conf 的手工修改不由 WAL
恢复。平台声明、证书、服务发现、KMS/IAM 与 DNS 也需要独立保存。
场景三:区域或控制域故障
区域故障不是“把同一恢复命令放到另一台机器”:
source region unavailable
local object store unavailable
local DNS/control plane unavailable
operator identity federation degraded
secrets/KMS endpoint unavailable
network dependency changed
capacity not pre-provisioned
如果数据库与备份仓库在同一机房、云账户、管理员凭据或密钥域,所谓
“远程对象存储”仍可能是共同故障。
区域场景的前提矩阵至少包括:
只有“对象在另一个 bucket”远远不够。
场景四:合规留存与法律冻结
合规问题不等于“把备份留久一点”。需要先区分:
operational recovery retention
为误删、故障与日常恢复保留
historical record retention
为审计、诉讼、监管或业务档案保留
legal hold
正常过期规则暂时不能删除特定材料
right-to-delete / minimization
到期后必须删除或不可再识别
物理备份以整个 cluster 为粒度,单个数据主体的删除很难立刻传播到所有历史
备份。合规方案可能需要:
- 受控的短期物理恢复窗口;
- 更长期、字段经过选择与脱敏的逻辑档案;
- 按租户/数据域分离;
- 密钥销毁策略;
- legal hold 与正常过期的冲突处理;
- 恢复访问的审批、审计和二次删除。
不要让 DBA 独自解释法律语义。数据 owner、安全、法务与平台团队要共同签署
retention class。
场景卡,而不是一句“灾备”
为每个场景写同一张卡:
id: accidental-delete-orders
trigger: confirmed destructive transaction against orders
authoritative_boundary:
source: audit event + restore point
timezone: UTC
scope: selected tenant and time range
maximum_tolerated_loss:
committed_correct_orders: zero
service_target:
historical cluster queryable: 60 minutes
repaired rows in production: 4 hours
dependencies:
- base backup and continuous WAL
- audit event identity
- isolated restore capacity
- reviewed logical merge procedure
stop_conditions:
- target transaction ambiguous
- WAL gap
- post-target correct orders cannot be reconstructed
proof:
- restored marker boundary
- row and amount reconciliation
- approved repair change
owner: data-platform + orders-domain
recovery-scenarios.json 提供本章四类机器可读样例。没有 stop_conditions
的 runbook 很容易在压力下把“不知道”伪装成“应该没问题”。
同一个事故可能需要两条恢复路径
例如误删订单:
path A: service continuity
current production remains online
path B: historical reconstruction
isolated PITR -> export -> compare -> repair
例如区域故障:
path A: promote designed DR replica
lower RTO, possibly different RPO/consistency contract
path B: restore off-site backup
slower, but independent historical recovery
HA、DR 与 backup 可以组合,但不能互相替名。
21.1.2 RPO、RTO、恢复粒度与保留周期
RPO:允许回退多远
令:
t_loss 事故发生或最后可信事实时间
t_recover 实际可恢复边界
时间口径可写为:
RPOactual=tloss−trecover但它不是简单的 backup interval:
daily base backup + continuous WAL
RPO 主要由 WAL 最后耐久位置决定,不是 24 小时
daily logical dump only
RPO 可能接近 24 小时
streaming replica
对主机故障可能接近复制延迟
对误删可能是 0 秒地复制了错误,无法提供历史点
生产 RPO 必须带场景:
primary process loss RPO
single-AZ loss RPO
repository outage RPO
operator logical error RPO
region loss RPO
“RPO = 5 分钟”却不说明事故类型,是不完整的合同。
WAL archive 的 RPO
连续归档场景中,粗略地:
RPOarchive≈tfailure−tlast durable independent WAL需要强调 durable 与 independent:
- WAL 在主库
pg_wal 中,不代表主机丢失后仍可取;
- archive command 返回成功,不代表对象存储副本已跨域;
- 对象写入完成,不代表 KMS/credential 在事故中可用;
pg_stat_archiver 的累计失败数不等于“当前失败”,要看最近成功/失败时序
和 backlog。
低流量系统还会遇到 segment 未填满。archive_timeout 或显式
pg_switch_wal() 可以缩短时间窗口,但过短会制造大量完整长度的归档对象。
RTO:恢复到哪种能力
完整恢复时间不是 restore 命令运行时间:
RTObusiness≥Tdetect+Tdecide+Tprovision+Tretrieve+Treplay+Tvalidate+Tcutover+Tdependency可以同时定义多个里程碑:
本章正式实验测到:
restore copy 2.758 s
start -> first read-only 0.963 s
start -> promoted 1.319 s
若只记录前两个数字,会漏掉验证、路由、应用依赖和积压,也会把只读窗口误报为
“恢复完成”。
恢复粒度
至少有四种粒度:
cluster
物理备份/PITR 常见粒度;包含 cluster 内所有数据库
database/schema/table
逻辑导出或从隔离物理恢复中再导出
row/business object
比较、审计、补偿与应用语义
service capability
read-only、read-write、reporting、limited tenant 等
PostgreSQL 物理 WAL 是 cluster 级历史。不能告诉恢复引擎“只重放某张表的
正确事务”。pgBackRest 的 database include/排除能力也不是任意表级 PITR;
选择性恢复的语义必须认真核对限制。
target 精度不等于 truth 精度
PostgreSQL 可按:
time
transaction ID
LSN
named restore point
immediate consistency
end of available WAL
停止恢复。但技术 target 再精确,如果业务事件不明确,也不能证明正确。
例如:
10:00:00 application request sent
10:00:01 database transaction committed
10:00:02 external payment confirmed
10:00:03 audit pipeline wrote event
“恢复到 10:00:02”到底包含什么,取决于时区、提交时间、inclusive 语义与
外部系统。命名点可以降低实验歧义,但生产误删通常只能事后从日志、XID、
LSN 或审计重建边界。
保留周期不是 PITR 窗口的同义词
假设“保留 14 天全备”,仍不能自动推出:
还需要:
- 至少一份在目标之前结束的可用基础备份;
- 从该备份起到目标的连续 WAL;
- timeline history;
- 未丢失的依赖备份;
- 可用的解密密钥和仓库权限;
- 与目标 PostgreSQL 版本兼容的恢复环境。
真实 PITR window 是这些集合的交集。
恢复点频度、版本数与日历跨度
三个常被混淆的量:
capture cadence
多久产生一个新逻辑/物理基线
version count
保留多少组 backup
calendar coverage
最旧可恢复事实距现在多远
retention_full=2 按 count 解释,与按 time 解释完全不同;差异/增量依赖还会
影响一组备份何时能安全过期。应把政策写成可测试的例子:
at 2026-08-01 12:00 UTC
must restore any named daily checkpoint since 2026-07-18
must restore any WAL time since 2026-07-25
must retain month-end logical archive for 7 years
然后定期真的选最旧目标恢复。
分级服务目标
不是所有数据库都应买同一种恢复成本:
分级的结果不是降低严谨性,而是让成本与真实损失匹配。
目标反推设计
对每个 tier 反推:
RPO
-> WAL archive latency / dump cadence / replication mode
RTO
-> restore bandwidth / provisioned capacity / runbook automation
granularity
-> physical, logical, audit or application repair path
retention
-> backup dependency graph + WAL policy + legal hold
failure domain
-> repository placement + credential + key independence
proof
-> drill frequency + oldest target + business invariant
这比先决定“全备还是增量”更接近工程问题。
21.1.3 逻辑、物理、快照和副本的职责边界
没有一种副本解决全部恢复问题
设计的关键是组合,而不是选一个“最好”的工具。
逻辑备份适合什么
pg_dump 在一个一致性快照中读取数据库,可以:
- 选择 schema/table;
- 以 custom/directory 格式并行恢复;
- 跨部分 PostgreSQL 版本迁移;
- 检视 DDL 与数据;
- 从隔离 PITR 中导出被误删的对象。
但逻辑备份:
- 不是数据目录文件副本;
- 不含用于物理重放的控制信息;
- 不能接到 WAL archive 上继续重放;
- 通常不覆盖所有 cluster-wide 配置与运行状态;
- 大库导出/导入和索引重建可能远慢于物理恢复;
- 恢复顺序、owner、extension、privilege 与 external dependency 仍要处理。
PostgreSQL 官方文档明确说明 pg_dump/pg_dumpall 不能作为连续归档物理
恢复链的一部分。
物理备份适合什么
物理备份复制 cluster 数据文件,并依靠 WAL 把不一致的文件时间切片恢复到
一致点。它保留:
all databases in cluster
catalogs and physical relation state
transaction status
extension physical objects inside cluster
system identifier lineage
它适合:
- 大型数据库快速整库恢复;
- PITR;
- 创建 standby/clone;
- 保留完整引擎语义。
但通常要求同一大版本与兼容架构/页格式,并以 cluster 为恢复单位。物理
恢复后再做逻辑选择,是误删恢复常见组合。
PostgreSQL 18 原生增量不要和工具增量混为一谈
PostgreSQL 18 的 pg_basebackup --incremental 依赖:
earlier backup manifest
WAL summaries covering the required LSN interval
pg_combinebackup at restore preparation
all earlier dependent backups
normal WAL recovery requirements
这是一套 PostgreSQL 原生机制。pgBackRest 的 full/diff/incr、block
incremental、bundle 与 repository metadata 是另一套实现和依赖图。
讨论“我们做增量备份”时,必须说清:
which tool
which version
dependency chain
restore assembly step
expiration owner
不能因为术语相同就互换 runbook。
快照要回答一致性
单卷快照可能在 block 层原子,但数据库语义还要问:
- PostgreSQL 是否运行中?
- 数据、WAL、tablespace 是否跨多个卷?
- 各卷快照是否同一 consistency group?
- 是否调用
pg_backup_start/stop 或工具协议?
- 快照克隆后如何恢复与识别 timeline?
- 快照控制面与主存储是否共同故障?
- 是否定期挂载并启动验证?
“云盘快照成功”不是数据库一致性的证据。一个 crash-consistent snapshot
也许能通过 crash recovery,但不能自动提供任意 PITR 或跨卷一致性。
Replica 为什么不是 backup
流复制的目标是把当前历史低延迟复制到其他实例:
primary DELETE wrong rows
-> WAL
-> replica replays same DELETE
它可以:
- 降低实例/主机故障的 RTO;
- 在同步策略下改善某些故障的 RPO;
- 分担读;
- 作为备份来源,降低主库读取压力。
它不能单独:
- 保留误操作之前的历史点;
- 对抗影响全部成员的错误配置;
- 对抗同一账户/区域/恶意管理员;
- 证明长期 retention;
- 替代隔离恢复。
延迟副本可增加逻辑错误反应窗口,但依然需要:
delay guarantee
pause/stop authority
monitoring that does not auto-heal away the delay
protection from direct reads/writes
independent backup for other failures
CDC 不是免费备份
事件流或 logical replication 能帮助重建某些数据,但要验证:
- 初始快照从哪里来;
- DDL 是否同步;
- TRUNCATE/DELETE 是否保留足够语义;
- sequence、large object 与 extension state;
- slot 丢失或 lag;
- exactly-once 是否只是消费端幂等;
- 下游是否共享同一个逻辑错误。
它更适合作为另一条可重建历史,而不是未经证明的整库恢复替代品。
一份常见的组合
HA replicas
handle selected availability failures
physical full/diff/incr + WAL
handle cluster restore and PITR
logical export
handle object-level portability and long-term selected records
audit/event history
identify business boundary and support repair
off-site immutable copy
handle control-domain and destructive repository loss
restore drills
prove that all of the above compose
“多种机制”不等于重复浪费;前提是每种都有明确场景和 owner。
选择问题
面对一个需求,按顺序问:
1. loss is logical, physical, site-wide, or compliance?
2. recover whole cluster or selected facts?
3. need historical point or latest state?
4. how independent must the copy be?
5. what PostgreSQL/version compatibility exists?
6. what target precision can be observed?
7. how will business correctness be proven?
8. what is the controlled return-to-service path?
可能的答案:
selected rows from yesterday
isolated physical PITR -> logical export -> reviewed merge
whole cluster after storage loss
physical backup + WAL -> replacement infrastructure
cross-major migration
logical dump/restore or logical replication, not physical replay
low-RTO host failure
HA promotion, while independent backup remains separate
seven-year selected archive
policy-specific logical record, not indefinite operational WAL by default
本节验收问题
在进入物理原理前,应能回答:
- 你的前三个恢复场景是什么?
- 每个场景的 authoritative boundary 来自哪里?
- RPO/RTO 的起止点和完成能力是什么?
- 哪些损失会被 replica 同步复制?
- 当前 retention 是否真的形成连续 PITR window?
- 配置、密钥、声明和应用依赖存放在哪里?
- 哪些结论只有实际 restore 才能证明?
如果答案仍是“每天有备份”,还没有完成设计。
小结
恢复体系从业务损失开始:
scenario defines truth
truth defines target
target defines mechanism
mechanism defines dependencies
dependencies define retention and placement
proof defines the drill
下一节进入物理链条:一份在线基础备份为什么能够是不一致的文件切片,
WAL 又如何把它变成一个可选择时间点的 PostgreSQL 历史。
返回本章目录 · 下一节:物理备份与 WAL 连续性 ·
查看全书目录 · 查看索引中心
21.2 物理备份与 WAL 连续性
物理 PITR 可以写成一条依赖式:
Recoverable(t)=BaseBackup(b)∧ContinuousWAL(bstart,t)∧ReachableTimeline(t)∧RuntimeCompatibility其中任何一项缺失,都不是“少恢复一点”,而可能是整条恢复链不可执行。
21.2.1 基础备份、检查点与一致性起点
为什么运行中的文件可以备份
在线复制数据目录时,文件并不处在同一个瞬间:
relation A copied at 10:00:01
relation B page modified at 10:00:02
relation B copied at 10:00:03
catalog copied at 10:00:04
单看文件副本,它可能不一致。PostgreSQL 的保证来自:
- 备份在一个已知 checkpoint 边界开始;
- 保存恢复所需的 backup metadata;
- 保存备份期间及之后所需的 WAL;
- 恢复时从 redo 起点重放到一致状态。
所以物理备份不是“恰好一致的一篮子文件”,而是:
recoverable file image + exact WAL obligations
checkpoint 在做什么
checkpoint 把一个恢复起点写入控制信息,并推动 dirty buffer 写盘。它不是
“把所有事务历史压成备份”,也不是备份完成点。
在线备份通常在 checkpoint 起点建立 redo requirement:
checkpoint redo LSN
<= backup start LSN
<= copied data interval
<= backup stop LSN
<= selected recovery target
恢复必须拥有从要求的 redo/WAL 起点到目标的连续记录。
pg_backup_start / pg_backup_stop
低层协议的核心顺序:
SELECT pg_backup_start(label => 'reviewed-label', fast => false);
-- keep this session alive while the file copy runs
SELECT * FROM pg_backup_stop(wait_for_archive => true);
关键语义:
- 调用
pg_backup_start 的连接必须保持;
fast=true 请求立即 checkpoint,可能增加 I/O;
- 文件复制期间数据库可以继续写;
pg_backup_stop 产生重要的 backup_label / tablespace_map 内容;
- primary 上默认等待所需 WAL 归档;
- 自制工具必须逐步验证,不能只在最后看 exit code。
正式系统通常使用 pg_basebackup 或 pgBackRest,而不是重新实现低层协议。
理解协议是为了判断工具证据。
backup_label 不是备注
backup_label 告诉恢复过程:
which backup session
start WAL file
start LSN
checkpoint location
start time
它和 tablespace mapping 是恢复输入,不是方便人看的注释。随意修改、漏拷或
把另一份备份的 label 混入,都会破坏血缘。
PostgreSQL 12 以后在线备份 label 通常作为 pg_backup_stop 输出交给备份工具,
而不是永久留在运行中 primary 的数据目录。不要照搬旧版“拷走
backup_label 文件”的手册。
pg_basebackup
pg_basebackup 通过复制协议取得运行中 cluster 的物理基础备份。常见能力:
plain or tar output
server/client compression
backup manifest
WAL streaming or fetch
rate limiting
tablespace mapping
standby signal/config generation
checkpoint mode
progress reporting
它需要具备 REPLICATION 权限或 superuser,并满足 pg_hba.conf 与
max_wal_senders。它只备份整个 cluster,不能只备份一个 database。
示意:
pg_basebackup \
--host=source \
--pgdata=/safe/new/path \
--format=plain \
--wal-method=stream \
--checkpoint=fast \
--progress \
--verbose
这不是本章 formal executor;正式实验使用 Pigsty 已交付的 pgBackRest。
--wal-method
概念上:
none
backup output 不带 WAL;必须另有完整 archive
fetch
在备份末尾从 source pg_wal 取所需 WAL;
必须确保 WAL 未被回收
stream
另开 replication connection 同步流式接收;
需要额外 wal sender
即使 backup 包含让自身达到一致点的 WAL,若要恢复到更晚时间,仍需要后续
连续 archive。
backup manifest 与校验
manifest 记录文件、大小、checksum、WAL range 等,可用
pg_verifybackup 验证一份 pg_basebackup:
pg_verifybackup /path/to/basebackup
它能证明文件与 manifest 一致、所需 WAL 结构满足其检查,却仍不能证明:
- repository credential 在事故时可用;
- PostgreSQL 能在目标环境启动;
- target 之后/之前的业务边界正确;
- extension/OS/配置依赖齐全;
- 服务能够切换。
校验是 restore proof 的一层,不是替代。
data checksum 的边界
PostgreSQL data checksum 可以在读页时发现某些 page corruption。备份工具也
可对仓库对象做 checksum。两者都重要,但:
checksum matches
means bytes match expected checksum
checksum matches
does not mean business values are semantically correct
错误事务生成的页面 checksum 完全正确。
tablespace 与外部路径
物理备份必须覆盖 tablespace。恢复环境要检查:
pg_tblspc symlink;
- target path 是否存在、为空、owner 正确;
- 不同主机路径是否需要 remap;
- mount 是否真的是预期设备;
- tablespace 与数据目录是否落在同一快照 consistency group。
忽略 tablespace 常导致“主数据目录恢复成功,启动时才发现一半对象缺失”。
从 standby 备份
从 replica 取备份可降低 primary I/O,但带来不同限制:
- 备份期间 standby 不能被 promote;
- backup 与 primary timeline/WAL archive 仍要协调;
- restartpoint 不等于 primary checkpoint;
- 低活动时某些增量条件可能不成立;
- replication lag 影响备份包含的历史;
- 工具必须知道如何从正确节点取得并验证 WAL。
“从副本备份”是容量/可用性选择,不自动增加备份独立性。
PostgreSQL 18 原生增量
PostgreSQL 18 支持:
pg_basebackup --incremental=/path/to/prior/backup_manifest ...
服务端依据 pg_wal/summaries 中的 WAL summary 判断改变的 block。恢复前用
pg_combinebackup 把 full 与后续 incremental 合成为可启动的 synthetic
full。
依赖链:
prior full
-> prior/current manifests
-> every required intermediate incremental
-> WAL summaries at backup time
-> pg_combinebackup
-> normal WAL recovery
PostgreSQL 不会替你管理哪些旧备份仍被新 incremental 依赖。过期策略删除
一个祖先,就可能让后代全部不可恢复。
版本兼容
物理备份通常用于同一 PostgreSQL 大版本。恢复环境还要匹配:
CPU architecture and page format expectations
PostgreSQL major
extension shared libraries
collation/locale providers
tablespace layout
configuration parameters needed during recovery
本章开发实验故意把 max_connections 从 source 的 500 降到 20,
PostgreSQL 18 拒绝恢复:
recovery aborted because of insufficient parameter settings
max_connections = 20 is lower than on the primary, where it was 500
这不是性能调优问题,而是 recovery safety check。正式 runner 携带 source 的:
max_connections
max_worker_processes
max_wal_senders
max_prepared_transactions
max_locks_per_transaction
不能因为“验证实例很小”就任意降低 WAL 所要求的上限。
21.2.2 归档、timeline history 与恢复链
WAL 是有序的物理历史
WAL record 在数据页落盘前耐久化,支持 crash recovery、streaming
replication 与 archive recovery。LSN 是逻辑日志位置,例如:
通常每个 segment 16 MiB,但 segment size 可在 initdb 时选择。WAL 文件名
编码 timeline、log 与 segment,不应靠截字符串之外的自造规则做跨配置运算;
使用 PostgreSQL 函数:
SELECT pg_current_wal_lsn();
SELECT pg_walfile_name(pg_current_wal_lsn());
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), '0/20000000');
SELECT pg_switch_wal();
连续性要求
一份 base backup 要恢复到目标 t,必须有:
[LSNrequired start,LSNt]上的每一段 WAL,以及中途 timeline 切换需要的 history。
缺一段不会得到“少几秒数据”;恢复通常停在 gap:
base -> WAL A -> WAL B -> [missing C] -> WAL D -> target
即使 D 在仓库里,也不能跨过 C。
archive_mode、archive_command 与 archive_library
归档要求:
wal_level >= replica
archive_mode = on
archive_command or archive_library configured
shell archive command 中:
%p source path relative to data directory
%f WAL filename
最重要的返回值合同:
exit 0
PostgreSQL believes WAL is durably archived and may recycle local file
nonzero
PostgreSQL retries
错误地返回 0 是数据丢失风险;持续返回非零则形成 backlog 与磁盘风险。
归档必须幂等,但不能覆盖不同内容
crash 后 PostgreSQL 可能再次提交同一个 WAL 文件。正确 archive sink 应:
target absent
atomically write and durably persist
target exists with identical content
return success
target exists with different content
return failure and alert
绝不能无条件覆盖。两个不同 system identifier 的 cluster 若误用同一 archive
namespace,可能产生相同 WAL 文件名却内容不同。
stanza、repository path、cluster identity 和权限隔离是防碰撞设计的一部分。
timeline 为什么存在
一次恢复或 promote 会从旧历史分叉:
timeline 7: A -> B -> C -> D
\
timeline 8: C' -> E -> F
新 timeline 保留父 timeline 与分叉 LSN 的 history。这样旧历史不会被
“回到过去再向前写”覆盖。
需要区分:
system identifier
initdb 生成的物理 cluster 血缘标识
timeline
同一物理血缘中的历史分支
checkpoint timeline
最近 checkpoint 记录的 timeline
current WAL timeline
当前写入 WAL filename 所在 timeline
恢复出来的物理副本应与 source system identifier 相同;完成 promotion 后
应进入一个新 timeline。本章 evidence 只记录关系:
system identifier relation = matches source
timeline 7 -> 8
不公开原始 identifier。
recovery 输入
现代 PostgreSQL 通过 signal file 进入恢复:
recovery.signal
archive recovery / PITR
standby.signal
standby mode, can continue waiting for WAL/stream
常见设置:
restore_command = 'fetch %f into %p'
recovery_target_time = '...+00'
recovery_target_name = '...'
recovery_target_xid = '...'
recovery_target_lsn = '...'
recovery_target_inclusive = on|off
recovery_target_timeline = 'latest'
recovery_target_action = 'pause'|'promote'|'shutdown'
同一轮只能选择一种 target kind。没有 target 时通常重放到可用 WAL 尾部。
命名点:
SELECT pg_create_restore_point('before_risky_change');
只在 primary 上有意义,并写入 WAL。它不是 backup;没有 base 与 archive,
名字本身什么也恢复不了。
inclusive / exclusive
恢复到 time、XID、LSN 时,要明确目标 record/transaction 是否包含。误删场景
常想恢复到 destructive transaction 之前:
target transaction known
recovery_target_xid = destructive xid
recovery_target_inclusive = false
但必须在实验中验证具体 target type 的语义。不要只凭自然语言“到某时刻”。
target timeline
恢复链可能包含多次 promote。latest 让恢复沿 archive 中可达的最新 timeline
继续;current/具体数字会限制选择。
危险例子:
choose an old backup
target time belongs to a child timeline
force target_timeline=current
结果可能根本到不了目标,或走错历史。选择前应画出 lineage:
backup timeline
-> history file
-> parent fork LSN
-> target timeline
hot standby 到达 consistent state 后可先接受只读连接,然后才到 target 并
执行 action。本章实际日志顺序:
consistent recovery state reached
database system is ready to accept read-only connections
recovery stopping at restore point
selected new timeline ID: 8
archive recovery complete
end-of-recovery checkpoint
database system is ready to accept connections
因此完成条件应为:
SELECT pg_is_in_recovery(); -- must be false
SHOW transaction_read_only; -- must be off
BEGIN;
CREATE TEMP TABLE write_probe(x int);
INSERT INTO write_probe VALUES (1);
ROLLBACK;
只读 SELECT 成功不是 promotion proof。
配置文件不在 WAL 历史里
恢复到过去不会自动恢复:
postgresql.conf external changes
pg_hba.conf
pg_ident.conf
Patroni YAML/DCS policy
TLS certificates
Pigsty inventory
DNS/proxy config
application secrets
这些必须由 versioned declaration、配置备份与平台自动化重建。把配置也塞进
data directory 不是充分答案,因为事故可能同时损坏或需要在新环境重写。
21.2.3 复制槽、归档失败与 WAL 保留者
谁让 pg_wal 不能回收
WAL retention 的常见“持有人”:
checkpoint/recovery requirement
archive not yet successful
physical replication slot
logical replication slot
wal_keep_size
backup in progress
standby/restartpoint needs
它们并不是同一种保护,也不能相互替代。
archive backlog
当 archive command 失败:
completed segment remains needed
-> retry
-> pg_wal grows
-> filesystem full
-> PostgreSQL PANIC/offline
数据库继续运行一段时间不代表故障无害。官方文档指出,pg_wal 所在文件系统
填满会导致 PANIC;事务不会因此神奇地归档到远端。
监控:
SELECT archived_count,
last_archived_wal,
last_archived_time,
failed_count,
last_failed_wal,
last_failed_time,
stats_reset
FROM pg_stat_archiver;
解释要注意:
failed_count = 21
last successful archive is after last failure
可能表示历史上失败过、当前已经恢复。不能仅因累计 count > 0 就报“当前失败”。
反之,长时间没有新 WAL 的系统,last_archived_time 老也未必故障。结合:
- current WAL segment;
- archive queue/backlog;
- WAL generation rate;
- repository maximum;
- disk free;
- recent command errors。
archive lag 与数据风险
两个间隙:
operational backlog
current WAL - last archived WAL
disaster data gap
source lost时,last business truth - last independently durable WAL
可用 byte 估计:
SELECT pg_wal_lsn_diff(
pg_current_wal_lsn(),
'last known archived LSN'
);
但 repository 通常按 segment 报告,边界内还有 partially filled segment。
生产 RPO 应用时间和业务 token 做补充。
为什么低流量也有 archive delay
archive command 通常对完成的 segment 工作。若写入很少:
important transaction commits
segment remains open
no archive object yet
选择:
archive_timeout 定期强制 switch;
- 关键变更后
pg_switch_wal();
- pgBackRest
check 触发/验证;
- streaming WAL 到独立系统。
代价是更多 segment object 和带宽。策略应由 RPO 与成本反推。
physical replication slot
physical slot 保护某个 consumer 尚未接收的 WAL:
SELECT slot_name,
slot_type,
active,
restart_lsn,
wal_status,
safe_wal_size,
inactive_since
FROM pg_replication_slots;
如果 consumer 永久消失而 slot 保留,WAL 可无限增长,除非
max_slot_wal_keep_size 设限。设限后 slot 也可能变为不可继续,需重建
replica。
槽保护 streaming consumer,不等于 archive 成功:
slot retained WAL on primary disk
!= independent disaster copy
logical slot 更容易被忽视
logical slot 还关联 catalog horizon:
- 保留 WAL;
- 可能保留 dead tuples/catalog rows;
- consumer lag 影响磁盘与 vacuum;
- failover/同步 slot 有版本与配置前提。
监控 slot 要看:
active
restart_lsn
confirmed_flush_lsn
wal_status
safe_wal_size
xmin/catalog_xmin
owner and consumer
没有 owner 的 slot 是容量事故候选。
wal_keep_size
wal_keep_size 是最近 WAL 的最低保留量,帮助无 slot standby 应对短暂断开。
它:
- 不是硬上限;
- 不保证某个 consumer 的准确位置;
- 不写远端仓库;
- 不提供长期 PITR;
- 不替代 slot 或 archive。
多个保留者叠加
最终 pg_wal 需要保留到最老需求:
LSNrecycle frontier=min(LSNcheckpoint,LSNarchive,LSNslots,LSNbackup,LSNstandby)概念上谁最老,谁控制回收边界。因此磁盘告警时不要只看“archive 正常”,
还要看所有 slot、backup lock 与 standby 状态。
本章沙箱事实
正式演练前后观察到:
archive_mode=on
archive command configured
repository stanza status=ok
one S3-compatible repository
target restore-point segment <= repository maximum segment
source timeline remains 7
restore timeline becomes 8
累计 archiver 历史包含早期失败,但最近成功晚于最后失败;本章没有把累计
失败数误判为当前中断。
故障处理顺序
archive backlog:
1. stop unsafe cleanup/expiration
2. measure pg_wal free space and growth rate
3. identify exact archive error and repository availability
4. preserve source WAL
5. restore archive path/credential/capacity
6. confirm backlog drains and repository maximum advances
7. prove a restore target, not just count successes
8. write incident and prevention
不要先:
delete pg_wal files
drop unknown slots
reset stanza
expire repository aggressively
这些动作可能把可恢复性问题变成不可恢复。
本节原生检查单
SHOW wal_level;
SHOW archive_mode;
SHOW archive_command; -- do not copy secrets into tickets/evidence
SHOW archive_timeout;
SHOW wal_keep_size;
SHOW max_slot_wal_keep_size;
SELECT * FROM pg_stat_archiver;
SELECT slot_name, slot_type, active, restart_lsn,
confirmed_flush_lsn, wal_status, safe_wal_size
FROM pg_replication_slots;
SELECT pg_current_wal_lsn(),
pg_walfile_name(pg_current_wal_lsn());
SELECT system_identifier
FROM pg_control_system(); -- compare securely; do not publish raw id
SELECT timeline_id, redo_lsn, checkpoint_lsn
FROM pg_control_checkpoint();
在 replica 上不能调用 primary-only current WAL 函数;使用 replay/receive
位置并标注 observation point。
小结
物理 recoverability 的核心不是“有一个 tar 包”,而是:
one reviewed physical lineage
+ complete base image
+ exact backup metadata
+ continuous WAL
+ reachable timeline history
+ compatible recovery runtime
下一节把这些对象放进仓库依赖图:full、diff、incr 应如何保留,怎样防止
正确的自动过期删除仍被后代依赖的祖先,以及为什么加密不等于不可变。
上一节:从恢复场景设计备份 · 返回本章目录 · 下一节:备份仓库与保留策略 ·
查看全书目录 · 查看索引中心
21.3 备份仓库与保留策略
仓库不是“放备份的目录”,而是一张有依赖、权限、密钥、过期与故障域的
历史图。一个对象存在,不代表它独立可恢复;一个对象过期,也可能让一串
后代失去意义。
21.3.1 全量、差异、增量与过期
三种备份的依赖
以 pgBackRest 术语:
full
不依赖同仓库的更早 backup set
diff
依赖最近 full,保存相对 full 的变化
incr
依赖最近一次可作为父级的 backup,保存相对父级的变化
示意:
F1
├── D1
│ ├── I1
│ └── I2
├── I3
└── D2
└── I4
F2
└── I5
恢复 I4 需要 F1 + D2 + I4;恢复 I2 需要对应链。工具 catalog 负责
依赖,但 retention policy 必须与这张图一致。
“增量”有多个层面
不要只看备份类型标签:
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”不一定
意味着仓库再次保存一份完整未去重字节。要区分:
logical database size
backup delta read
repository delta written
repository total referenced size
本章 formal full:
logical size 36,121,841 bytes
repository delta 4,539,288 bytes
这反映当前 pgBackRest block/bundle/compression 配置,不代表任何生产数据的
压缩率。
differential 的恢复深度
diff 只依赖 full,所以常用于:
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:
Trecovery≈Tretrieve backup+Tassemble+Treplay WAL+Tcheckpoint因此:
- 更频繁 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 的解释:
count
保留多少 full backup set
time
以天为阈值保留 full
按 count=2 时,新 backup 要先成功,之后才可能 expire 最旧,因此瞬时可见
三份 full。按 time=20 时,需要存在至少一份达到对应年龄的 full 才形成过期
条件。不要把配置数字直接写成没有验证的“覆盖天数”。
differential retention
repo1-retention-diff 按数量控制 diff。依赖被过期时,相应 incremental
也要一起处理。
政策样例:
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。
原则:
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 前,至少验证:
- 目标 retention 用例已写成例子;
- catalog 能画出 full/diff/incr dependency;
- newest backup 成功后才触发过期;
- repository 容量允许一次失败重试与过渡峰值;
- legal hold 不会被普通规则删除;
- off-site/immutable copy 的过期独立受控;
- 定期恢复最旧目标;
- dry-run 输出有人审阅;
- 时钟、时区与对象 lifecycle policy 一致;
- 删除权限与写入/恢复权限分离。
对象存储 lifecycle 的隐形删除
即使 pgBackRest retention 正确,bucket lifecycle 也可能:
- 提前删除 object/version;
- 转冷存储导致 RTO 激增;
- 删除 multipart/metadata;
- 与 legal hold 冲突;
- 在 repository catalog 不知情时改变可用性。
基础设施 lifecycle 必须成为同一份恢复设计的受控输入。
最旧目标演练
只恢复 latest backup 不能验证 retention:
choose oldest required target
-> identify required backup chain
-> retrieve cold/off-site objects
-> obtain historical key
-> replay full WAL span
-> validate business marker
生产演练轮换:
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 校验、加密、不可变与异地副本
四个不同属性
integrity
bytes 未损坏/未被替换
confidentiality
未授权者不能读取
immutability
在保留窗口内,包括高权限主体也不能轻易删除/改写
availability
事故时对象、密钥、网络和权限仍可用
checksum 提供部分 integrity;加密提供 confidentiality;它们都不自动提供
immutability 或 availability。
校验的层次
每层都必要,但结论边界不同。
pgbackrest check
check 用于验证 stanza 配置与 archive path。典型:
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
在恢复前安全失败。这个失败保留了两个教学点:
same tool != every command accepts same option
successful check != successful restore
正式成功运行修正命令后继续。
加密在哪里
可能的层次:
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
普通权限:
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 会:
- 无法删除敏感数据;
- 产生不可控成本;
- 阻塞环境清理;
- 与法律删除义务冲突。
因此需要:
retention class
legal hold process
minimum/maximum lock
authorized bypass
evidence and audit
test bucket before production
不可变是政策与控制面组合,不是一个布尔开关。
异地副本的独立性
“异地”至少检查:
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 只是启发式
常见经验:
3 copies
2 media/system types
1 off-site
它提醒独立性,但不能替代场景合同。三份都被同一 credential 删除,数量没有
意义;一份真正不可变、离站且可恢复的副本,可能比十份同域复制更有价值。
repository namespace
避免不同 cluster 冲突:
repository
-> stanza
-> database/system-id lineage
-> archive id
-> timeline/WAL
不要把另一个 initdb 出来的 cluster 伪装成旧 cluster 继续向同一 archive
namespace 写入。pgBackRest stanza metadata 与 system ID 检查是保护层;
权限与路径隔离仍要做。
沙箱边界
本章仓库:
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
所以正式结论带:
EX21-REPOSITORY-NOT-IMMUTABLE
能够真实恢复,并不抹掉仓库共同故障。
21.3.3 容量预算、失败告警与责任人
容量不是数据库大小乘份数
粗略预算:
Capacity=Bfull+∑Bdiff+∑Bincr+WALwindow+Metadata+Versions+SafetyMargin还受:
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。
两个增长率
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 的数据库,备份策略不同。
原生观测:
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。
带宽预算
需要同时考虑:
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 都不同。
容量水位
至少建立:
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
阈值应给行动时间:
warning when forecast leaves enough time to provision
critical before next backup/archive can exhaust capacity
固定 80%/90% 可能对高速增长系统太晚。
备份失败不是一个布尔告警
分类:
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
示意:
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 失败:
- 确认 archive 仍连续;
- 确认
pg_wal 与 repository 容量;
- 保存错误上下文与 catalog;
- 判断现有 restore window 是否仍满足;
- 修复依赖后重试;
- 不要先 expire “腾空间”;
- 若 RPO 已越线,升级 incident。
当 archive 失败:
- 以磁盘耗尽预测为首要风险;
- 保护未归档 WAL;
- 恢复 repository path;
- 确认最大 WAL 前进;
- 安排隔离 restore 验证链条。
RACI
一份实际 owner map:
如果 backup 告警只有“DBA 群”负责,区域、密钥、应用验证很可能无人负责。
每日、每周、每季
示意节奏:
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。
可审计政策模板
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,而不只是配置愿望。
小结
仓库的工程对象是:
backup dependency graph
+ continuous WAL window
+ integrity
+ confidentiality
+ immutability
+ independent availability
+ capacity
+ ownership
下一节进入恢复动作本身:怎样从候选 backup 中选择真正能到达 target 的一份,
如何在启动前验证 lineage/WAL,以及为什么 PostgreSQL “一致”仍不足以交付。
上一节:物理备份与 WAL 连续性 · 返回本章目录 · 下一节:恢复流程与验证 ·
查看全书目录 · 查看索引中心
21.4 恢复流程与验证
恢复是一场受控的数据分叉:
select historical lineage
-> copy base
-> replay WAL
-> stop at boundary
-> possibly promote onto new timeline
-> decide what may consume this history
每一步都可能生成一个能启动、却不应交付的实例。因此流程必须先写停止条件,
再写命令。
21.4.1 选择备份集、目标时间和恢复位置
先冻结事故事实
开始恢复前,保留:
incident start and detection time in UTC
suspected destructive transaction/request
source cluster identity and current timeline
current repository catalog
archive maximum and gaps
relevant application/audit logs
last known correct business marker
authority and requested outcome
不要一边猜 target,一边让日志、WAL、对象 lifecycle 和源状态继续变化而没有
快照证据。
target 不是一句“十分钟前”
把 target 写成结构化决策:
kind: name
value: before_release_20260729
inclusive: not-applicable
timezone: UTC
source_evidence:
- pg_create_restore_point result
- release change record
expected:
present:
- schema version 41
- business token A
absent:
- migration transaction B
approved_by:
- incident commander
- data owner
时间 target 应包含 offset:
2026-07-29 20:10:44.000+00
避免依赖 session/local timezone。
target 类型选择
known pre-created boundary
name
known destructive transaction
xid + inclusive=false, after verifying identity
known physical log point
lsn
only trusted event time
time + explicit timezone/inclusive
need earliest consistent database
immediate
need latest independently archived state
end of archive, with RPO evidence
XID 会 wrap;日志中同一个数字必须与正确 cluster/epoch/时间关联。LSN 精确但
不直接表达业务。time 易懂但可能有 clock/commit ambiguity。
选择能到达 target 的 backup
候选 base 必须:
finish before or otherwise be valid for target
belong to same physical lineage
have all dependent backup objects
have continuous required WAL to target
have reachable timeline history
have usable encryption key
fit target runtime/version
对 time target,pgBackRest 可以选择一份结束时间早于 target 的可用 backup。
对 name/XID,工具不能总是自动知道 target 落在哪份历史中;pgBackRest
User Guide 明确提示 name/XID target 无法自动选择 backup。正式 run 因此用
--set=<exact-label> 固定刚创建的 full。
“最新备份”可能太新
误删发生在 10:00:
backup A ends 09:00
backup B starts 11:00
选择 B 无法回到 10:00 之前,因为其文件状态已经包含误删;需要 A 加 WAL。
“latest”不是无条件正确。
backup set 与 recovery target 分离
--set
chooses base/dependency set
--type / --target
chooses where WAL replay stops
一个是起点,一个是终点。混淆会导致:
- 起点晚于目标;
- 自动选择错误;
- 恢复到 WAL 尾部而不是事故前;
- 以为 backup label 就是业务时刻。
恢复位置
优先顺序:
new host/failure domain
best isolation
same host, new path/process/socket/port
useful lab compromise with explicit exception
overwrite original data directory
destructive replacement only, separate authority
日常验证和误删取数应选择新环境。覆盖原集群会:
- 消灭当前证据;
- 影响服务;
- 把 target 错误变成二次事故;
- 与 Patroni/DCS 冲突;
- 让失败回退更难。
空间预算
至少:
Space≥RestoredData+WALWorkingSet+Temp+Logs+SafetyMargin若保留源数据副本,可能需要两倍以上。tablespace、sparse file、reflink、
object cache 与 filesystem reserved blocks 都要计入。
恢复前:
df -h
df -i
findmnt
lsblk
确认 path 真正位于预期设备,而不是 root filesystem 上一个空目录。
权限与隔离
恢复目录:
owner postgres
mode 0700
not symlink
new and empty
not /pg/data
not a Patroni member path
接入:
listen_addresses=''
private Unix socket mode 0700
custom restrictive pg_hba
distinct port
no service registration
no DNS/VIP/proxy route
数据副本本身可能含生产敏感数据;“测试恢复”不能降低访问控制。
archive push 隔离
PITR promote 会产生新 timeline。验证实例若继续向 source repository
archive,可能污染共享历史。pgBackRest 官方指南建议,对会 promote 但不成为
新 primary 的 reporting/testing cluster 使用:
正式实验既在 restore option 中设置,又查询有效:
SHOW archive_mode; -- off
仅修改 archive_command='' 会让 WAL 积在本地,并不等同于清晰的验证实例
policy。
authority
恢复至少分三种授权:
read-only catalog/capture
no database or repository mutation
isolated restore drill
synthetic source writes + new backup/path/postmaster
production replacement/cutover
stops/overwrites/reroutes real service
本章 formal 只有第二种沙箱授权。不能拿它的确认 token 去生产。
21.4.2 启动前核对时间线与 WAL 完整性
preflight 清单
在 postgres 启动前确认:
exact source/stanza/system lineage
exact backup label and type
backup status and dependency completeness
backup start/stop WAL
target kind/value/inclusive
target WAL covered by archive
timeline history available
restore_command configured
recovery.signal present
standby.signal absent unless desired
tablespace path and ownership
PostgreSQL major/binaries/extensions
recovery-critical max settings
network/HBA/archive isolation
log destination writable
把它写入 evidence,不要只在人的终端滚过。
repository catalog
pgBackRest:
sudo -iu postgres \
pgbackrest --stanza=pg-test --repo=1 --output=json info
检查:
stanza status code
database/repository id
PostgreSQL version
backup label/type/error
start/stop timestamp
start/stop archive
logical/repository bytes
archive min/max by archive id
locks
不要把输出中的 repository secret 或 raw system identifier 复制进公开 evidence。
WAL 文件名比较的限制
同一 archive ID、固定 segment size 和 timeline 语境中,固定长度 WAL filename
可帮助判断 target segment 是否不晚于 max。更完整的验证需要:
- 确认中间没有 gap;
- 确认 history file;
- 让 restore path 实际逐段读取;
- 对 object checksum;
- 记录 archive ID 与 lineage。
只有 max >= target 不能证明中间全在。正式实验同时跑 pgbackrest check
和真实 restore;真实 replay 是最终 gap detector。
restore_command
恢复过程请求 WAL:
restore_command = 'pgbackrest ... archive-get %f "%p"'
返回合同与 archive push 类似:
0
requested file delivered
nonzero
file unavailable; PostgreSQL may try pg_wal/stream/next source as applicable
不要让 command 把其他 cluster 同名 WAL 放入 %p。
signal file 与 generated settings
pgBackRest restore 会生成 postgresql.auto.conf recovery settings,并创建
recovery.signal。检查:
test -f "$PGDATA/recovery.signal"
test ! -f "$PGDATA/standby.signal" # for this promoted PITR design
sed -n '1,200p' "$PGDATA/postgresql.auto.conf"
输出可能包含路径或凭据参数,证据应做 secret-safe 投影,而不是无脑上传。
同一 system identifier
物理 backup 和 source 应同一 lineage:
SELECT system_identifier
FROM pg_control_system();
比较关系:
如果不同:
- backup 来自另一 cluster;
- stanza/path 混淆;
- evidence 目标错误。
不要“修” system identifier 让它相等;停止并调查。
timeline 必须可达
记:
backup timeline = Tb
target timeline = Tt
要求存在从 Tb 到 Tt 的合法 history path。PITR promote 后的新
timeline Tn 应满足:
Tn>Tt本章:
source/target timeline 7
restored promoted timeline 8
如果 promotion 后仍报告 7,要确认观察的是 current WAL timeline、checkpoint
timeline 还是 recovery 尚未完成。
checkpoint timeline 会滞后
pg_control_checkpoint() 报最近 checkpoint。一个 replica 可能已在接收/重放
更新 timeline,但其 checkpoint control info 仍较旧。第 20 章已遇到这种
情况。
因此:
primary current WAL filename timeline
Patroni TL
replica receive/replay evidence
checkpoint timeline
要标注 observation semantics,不能强行要求所有数字在任意瞬间相等。
recovery-critical 参数
WAL 记录某些 source 参数。恢复实例不能把它们设得更低。重点:
max_connections
max_worker_processes
max_wal_senders
max_prepared_transactions
max_locks_per_transaction
正式脚本先查询 source,再在 isolated start 中显式携带。第一次开发启动因
max_connections=20 < 500 被 PostgreSQL 正确拒绝,未修改源集群。
这类失败的 SOP:
stop
read PostgreSQL FATAL/DETAIL/HINT
compare with captured source settings
correct isolated runtime
start as a new reviewed attempt
never weaken source or edit WAL/control data
extension 与 preload
物理 backup catalog 可能引用 extension。恢复环境若缺 .so:
- startup 可能因
shared_preload_libraries 失败;
- 查询对象可能缺函数/type;
- background worker 可能连接外部系统。
验证实例需要决定:
install exact extension packages
or explicitly disable reviewed preload components
本章 synthetic fixture 不依赖 preload,因此 isolated instance 设:
shared_preload_libraries = ''
这不能证明生产 extension 全部可用;属于本章小数据边界。
不让恢复实例自动加入平台
同一 cluster_name 不是 Patroni membership,但为避免混淆,本章使用:
cluster_name = 'pg36-ch21-restore'
同时:
no Patroni process
no DCS registration
no HAProxy service
no exporter discovery
no archive push
如果目标是正式替换 cluster,加入平台是另一个经过审查的阶段。
日志是证据
关注顺序:
starting backup recovery
restored history/WAL from archive
starting point-in-time recovery to ...
redo starts
consistent recovery state reached
ready to accept read-only connections
recovery stopping at target
selected new timeline
archive recovery complete
end-of-recovery checkpoint
ready to accept connections
错误:
requested WAL not found
recovery ended before configured target
invalid checkpoint record
insufficient parameter settings
could not load library
tablespace path failure
permission denied
不要只保存最后一行 database system is ready。
两阶段 readiness
正式 runner:
- 用
pg_ctl -w 等到第一条连接;
- 立即记录
pg_is_in_recovery() 与 transaction_read_only;
- 继续轮询到 recovery=false;
- 查询 effective isolation;
- 做 rollback-only write;
- 才声明 promotion complete。
观测:
first connection:
in_recovery=true
transaction_read_only=true
355.896 ms later:
in_recovery=false
transaction_read_only=false
这不是理论角落,而是本章真实运行结果。
21.4.3 数据库一致不等于业务数据正确
引擎一致性
PostgreSQL recovery complete 能说明:
- WAL record 可重放到一致点;
- control/catalog/transaction state 满足引擎;
- 数据库可按当前模式接受连接;
- promotion 时建立了新 timeline。
不能说明:
- target 是事故前正确边界;
- 所有正确交易都在;
- 错误交易都不在;
- 外部系统一致;
- 应用 schema/code 匹配;
- 秘密与权限适合交付;
- 报表金额正确。
业务不变量
为每个服务维护可执行 invariant:
orders
no orphan order lines
order total = sum(lines)
accepted payment token unique
known checkpoint token present
destructive release token absent
ledger
debits = credits per journal
immutable entries not missing
sequence/event continuity
tenant SaaS
no row crosses tenant boundary
tenant counts match reference
row-level security policies installed
SQL 示例:
SELECT order_id
FROM order_line l
LEFT JOIN orders o USING (order_id)
WHERE o.order_id IS NULL
LIMIT 1;
SELECT journal_id
FROM ledger_entry
GROUP BY journal_id
HAVING sum(debit) <> sum(credit)
LIMIT 1;
零行是某项证据,不是万能健康。
正向与反向标记
一个强 boundary test 同时要求:
before target marker present
at/allowed target marker present
after target marker absent
本章:
base present
keep present
discard absent
只验证 keep 存在,可能实际恢复到了 WAL 尾部,discard 也在;反向断言能
发现 overshoot。
token 要不可混淆
正式 marker:
run_id + stage
unique token
database commit timestamp
primary key(run_id, stage)
unique(token)
恢复后按 exact run ID 查询,避免把旧演练行当成本轮成功。
验证 schema 与语义
除了行:
- schema version;
- extension version;
- constraint/index validity;
- owner/privilege;
- RLS policy;
- sequence;
- collation;
- function/trigger;
- materialized view freshness;
- partition attachment;
- large object。
有些对象在物理 backup 中存在,但应用新版本可能期待更晚 schema。历史
database 与当前 application code 不能直接组合。
外部系统
数据库可能回到 10:00,消息队列、对象存储、支付平台仍在 10:30:
database says payment pending
payment provider says captured
database row absent
object file already created
database event offset rewound
consumer has processed later events
恢复计划必须决定:
- 外部系统也回退?
- 数据库追赶?
- 做补偿/对账?
- 暂停哪些写入?
- replay event 是否幂等?
PITR 不能跨系统自动保持分布式一致。
误删取数的安全合并
推荐路径:
isolated historical restore
-> validate historical target
-> export only affected data
-> normalize identifiers/format
-> compare against current
-> reviewed repair transaction
-> audit and reconcile
不要把历史 cluster 暴露给普通应用写入;它的 sequence、outbox 与定时任务
可能重新发出旧动作。
可在隔离实例中:
disable external network
disable schedulers/background workers
use read-only role for analysts
export with COPY/pg_dump
record checksum/count
本章为了验证 promotion 做一次 temporary table 回滚写,不产生持久业务写。
service cutover 是另一个 gate
在真正 replacement 场景:
freeze or fence old writer
validate candidate
configure identity/secrets
register monitoring
update routing
drain/reconnect clients
run smoke/invariants
reconcile unknown outcomes
monitor backlog
retain old evidence
本章不执行 routing change,所以:
database/data proof = accepted with exceptions
service cutover = not run
第 22 章继续连接与服务合同。
验证矩阵
证据包
至少包含 secret-safe:
authority and exact target
source preflight
repository/catalog projection
backup label and WAL range
target decision
restore options and path
recovery phase timestamps
effective settings
lineage relation and timeline
business queries/results
source postflight
shutdown state
exceptions
counterexamples
review hashes
不要包含:
repository key
cipher pass
database password
raw secret inventory
unnecessary raw system identifier
失败也是证据
本章有两次开发/正式前失败:
development:
max_connections lower than source
PostgreSQL rejects recovery start
first formal attempt:
pgBackRest check given unsupported --repo=1
pgBackRest exits 31 before restore
两次都没有覆盖 source,也没有留下运行中的 isolated postmaster。我们修复
runbook,而不是删除失败痕迹后假装一次成功。
成熟流程会统计:
failed drills
failure phase
time to diagnose
unsafe side effects
runbook correction
repeat proof
完成定义
restore command exit 0 insufficient
PostgreSQL accepts read-only insufficient
PostgreSQL promoted engine milestone
business boundary passes data milestone
source remains healthy safety milestone
isolated instance stopped containment milestone
service controlled and reconciled service milestone
本章正式到 containment milestone,服务 cutover 保持未运行。
小结
恢复的核心动作不是“启动一个旧数据库”,而是连续回答:
which history?
which target?
which evidence?
which isolation?
which completion state?
which business truth?
which authority to expose it?
下一节把流程映射到 Pigsty 和 pgBackRest:仓库、调度、凭据、观察入口以及
为什么平台自动化应承载合同,而不是掩盖 PostgreSQL 原生证据。
上一节:备份仓库与保留策略 · 返回本章目录 · 下一节:用 pgBackRest 交付备份策略 ·
查看全书目录 · 查看索引中心
21.5 用 pgBackRest 交付备份策略
Pigsty 负责把 PostgreSQL、pgBackRest、仓库、归档、调度、日志和监控组合成
可交付基线;pgBackRest 负责 backup/archive/restore 机制;PostgreSQL 仍然
定义 WAL、recovery、timeline 与业务数据语义。
三层不要混淆:
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 仓库、策略、调度与凭据
从 Pigsty 声明开始
当前 Pigsty 的核心入口包括:
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 文档给出的常见方式:
local
local POSIX repository, default path /pg/backup
minio
S3-compatible repository, optional MinIO service
custom
pgBackRest supports other repository backends/config
职责差异:
pgbackrest_method=minio 不自动证明 MinIO 位于独立故障域。
stanza
pgBackRest stanza 把一个 PostgreSQL cluster 与 repository metadata 关联:
pgbackrest --stanza=pg-test stanza-create
pgbackrest --stanza=pg-test check
pgbackrest --stanza=pg-test info
Pigsty 在启用备份组件时交付配置/stanza。current 文档给出:
用于对已存在 cluster 启用相关 subtask;真正执行前:
- 固定 Pigsty repo/tag;
- 使用正确 inventory 与 limit;
- 先 diff/plan 变更;
- 确认不会删除/重建现有 stanza;
- 保护 secret output。
删除 cluster 与 stanza
Pigsty 文档提示,移除 primary 时可能删除 pgBackRest stanza,可用相应参数保留
backup。删除 cluster 的动作与删除 backup 历史是两个授权:
remove database service
!= authorize deleting recovery history
所有 pgsql-rm.yml / pg_rm_backup 动作都应进入 destructive review。本章
不执行。
backup 命令
Pigsty 提供 /pg/bin/pg-backup:
pg-backup # documented default/incremental behavior
pg-backup full
pg-backup diff
pg-backup incr
原生 pgBackRest:
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 声明:
pg_crontab:
- '00 01 * * 1 /pg/bin/pg-backup full'
- '00 01 * * 2-7 /pg/bin/pg-backup'
本章沙箱实际观察:
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。演练切换后检查:
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:
PostgreSQL archive command
-> pgBackRest spool queue
-> repository worker
好处是降低 postmaster archive command 的同步等待;代价是多一层队列状态。
观察:
spool path
archive-push async log
queue size/age
repository max WAL
pg_stat_archiver
pg_wal free
验证 restore 时应使用独立 spool path,避免测试实例与 live archive worker
共享状态。本章:
/data/pg36-ch21-restore/<run>/spool
凭据
典型 secret:
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 应验证:
TLS enabled
expected CA
hostname/SAN
certificate rotation
clock
no silent downgrade
本章沙箱配置有 repository CA path,但未把完整 transport security 作为
生产验收;第 23 章继续。
声明与有效状态
三份证据:
inventory declaration
generated /etc/pgbackrest config
effective command/catalog behavior
不能只看其中一个。生成配置可能漂移,声明可能未应用,运行命令也可能被环境
变量或额外 conf.d 覆盖。
安全投影应保留:
stanza
repo type/path class
endpoint class
cipher type
retention type/value
block/bundle
archive async
spool path
去掉所有 secret value。
21.5.2 备份状态、归档状态与容量观察
pgbackrest info
人读:
sudo -iu postgres \
pgbackrest --stanza=pg-test --repo=1 info
机器读:
sudo -iu postgres \
pgbackrest --stanza=pg-test --repo=1 --output=json info
关注:
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 解读
示例:
常见 suffix:
F full
D differential
I incremental
不要只解析名字推断成功;同时看 catalog error=false、status、archive 和
实际恢复。
check
sudo -iu postgres \
pgbackrest --stanza=pg-test --log-level-console=info check
它能触发/检查 archive path 与配置。不同 command 的 options 不完全相同:
info/backup/restore accept --repo=1
check in pgBackRest 2.59.0 rejects --repo=1
把一串“通用参数”复制给所有命令是危险习惯。
PostgreSQL 原生交叉验证
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());
对照:
PostgreSQL last archived
pgBackRest archive max
current WAL
spool queue
不同 observation 可能短暂错开,要用时间戳和 queue 解释。
累计失败数
本章沙箱:
archived_count > 0
failed_count = 21
last successful time > last failed time
这说明过去有失败,当前最近归档成功。正确告警不是:
failed_count > 0 forever critical
而是:
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
计算:
now - latest successful backup stop
还要按类型:
latest full age
latest diff age
latest any backup age
oldest retained full
一份最新 incremental 可能依赖过旧 full;只看 latest label 隐藏 chain 风险。
archive freshness
低流量时:
last archive time old
current WAL same segment
不一定 backlog。高流量时:
last archive time recent
but repository is many segments behind
仍可能越过 RPO。结合 rate 和 segment distance。
容量
源端:
df -h /pg/data /pg
du -sh /pg/data/pg_wal
repository:
used bytes
object count
growth forecast
version/lock overhead
cold tier
retention expiry
restore target:
本章 .13 的 /data 有充足空间,正式 restore 只有约 36 MB;这不能证明
生产大库空间规划。
日志
Pigsty 常见 pgBackRest log path:
分类:
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。
观察矩阵
本章 evidence capture
capture.py 默认只读:
PG36_EVIDENCE_DIR=/new/path \
static/labs/ch21/task.sh capture
它采集:
Patroni topology
PostgreSQL archive/current WAL/settings
sanitized pgBackRest catalog
no raw system ID
no credential
它明确输出:
recoverability = not-proven-by-capture
因为 capture 没有执行 restore。
21.5.3 在隔离目标而不是原集群上恢复
Pigsty 的恢复入口
当前 Pigsty 文档提供:
manual pg-pitr prompt/script path
pgsql-pitr.yml playbook path
文档说明,熟悉配置时可使用自动 playbook,否则建议逐步手工方式。其本质
原因是恢复 target、source 与 destructive action 需要人理解,不应让
automation 隐藏。
示意:
./pgsql-pitr.yml \
-e '{"pg_pitr": {"time": "2026-07-29 20:10:44+00"}}'
这类 playbook 可能停止、清空、重建目标 instance。不要在 live cluster 上
为了练习运行。
Pigsty pg_pitr 映射:
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
本章目标是验证备份,而不是替换服务。正式设计:
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 恢复,以精确控制隔离边界。
结构化示意:
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 含:
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
显式覆盖:
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:
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
pg_ctl -D "$restore/data" -w start
可能在只读 consistent state 返回。正式脚本随后:
SELECT pg_is_in_recovery(),
current_setting('transaction_read_only'),
current_setting('archive_mode'),
current_setting('listen_addresses');
等待:
recovery=false
transaction_read_only=false
archive_mode=off
listen_addresses=''
再做 rollback write。
停止并保留
验证后:
pg_ctl -D "$restore/data" -w -m fast stop
检查:
postmaster.pid absent
Unix socket absent
TCP listener absent
restore directory present
live replica still streaming on 5432
保留目录支持审计,但不是长期服务。删除由单独 reset:fixture 授权。
防误操作设计
task.sh:
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/读者为了“跑全套检查”意外再做备份
或启动恢复。
版本迁移
迁移到新版本时,工时不只改命令:
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 列出 production
仍需补的 evidence,不把沙箱通过升级为生产。
小结
Pigsty 与 pgBackRest 的价值,是把可恢复性机制交付成一致、可观察、可自动化
的系统。正确使用方式是:
declare with Pigsty
inspect with pgBackRest + PostgreSQL
prove with isolated restore
govern with explicit authority
下一节执行完整实验,并逐项解释 formal output、两个安全失败、十四个反例和
生产 gate。
上一节:恢复流程与验证 · 返回本章目录 · 下一节:实战:完成一次隔离恢复演练 ·
查看全书目录 · 查看索引中心
21.6 实战:完成一次隔离恢复演练
这是一次真实运行过的物理恢复,不是伪造的示例输出。
实验完成:
fresh synthetic marker
fresh full pgBackRest backup
post-backup marker
named restore point
post-target marker
forced WAL switch + pgBackRest check
exact-label restore to a fresh isolated path
read-only and promotion phase measurement
positive/negative business boundary
lineage and timeline proof
isolated shutdown
source postflight
14 adversarial counterexamples
安全边界:
local nonproduction sandbox only
production data/traffic forbidden
source cluster not stopped
live /pg/data not touched
no DCS/Patroni membership for restored copy
no TCP listener
archive push disabled
no routing change
no cleanup during acceptance
production approval pending
21.6.1 创建已知业务检查点并执行备份
先读合同
四个入口:
exact target:
pg36-l2-vagrant/pg-test
Pigsty v4.5.0
PostgreSQL 18.6
pgBackRest 2.59.0
pg-test-1 primary
pg-test-2/3 streaming replicas
任何 version/member/role drift 都先停止。
风险分级
普通 all 不包含 drill 或 reset。
无授权必须拒绝
PG36_EVIDENCE_DIR=/new/empty/path \
static/labs/ch21/task.sh drill:pitr
结果:
exit=77
refusing PITR drill: exact target, nonproduction, data, traffic,
and confirmation guards are required
拒绝发生在任何 marker/backup/restore 前。
chapter-19 gate
formal wrapper 先运行:
PG36_CH19_INVENTORY=/absolute/mode-0600/reviewed.yml \
PG36_EVIDENCE_DIR="$run/preflight-ch19" \
static/labs/ch19/task.sh all
要求:
hosts=4-distinct
pg-meta one primary
pg-test one primary + two streaming replicas
secret values redacted
sandbox_l2=accepted-with-exceptions
production_ch19_gate=pending
private inventory 不复制到 evidence。它用于验证 exact declaration,不应使用
公开 placeholder 去做真实部署。
source preflight
在 DDL 前采集:
Patroni exact member set
pg-test-1 only leader/running
pg-test-2/3 replica/streaming
replay lag <= 1 MiB
source in_recovery=false
archive_mode=on
current WAL segment/timeline
recovery-critical settings
sanitized pgBackRest catalog
source system identifier 只保存在 runner 内存用于 equality,evidence 不导出
raw value。
fixture
setup.sql:
CREATE SCHEMA IF NOT EXISTS pg36_ch21;
CREATE TABLE IF NOT EXISTS pg36_ch21.recovery_probe (
run_id text NOT NULL,
stage text NOT NULL
CHECK (stage IN ('base','keep','discard')),
token text NOT NULL UNIQUE,
committed_at timestamptz NOT NULL DEFAULT clock_timestamp(),
PRIMARY KEY (run_id, stage)
);
每轮 run ID:
run_YYYYMMDDTHHMMSSZ_<8 hex>
路径与 SQL token 只接受这个 allowlist,避免 path/command injection。
三阶段顺序
insert base
-> take full backup
-> insert keep
-> pg_create_restore_point(name)
-> insert discard
-> pg_switch_wal
-> pgBackRest check
顺序的逻辑:
marker 不是 sleep
我们不靠:
猜测时间边界,而是保存:
run ID
unique token
database commit timestamp
restore-point LSN
target WAL filename
因此验证的是 transaction/WAL 边界。
fresh full
formal command:
sudo -iu postgres pgbackrest \
--stanza=pg-test \
--repo=1 \
--type=full \
--log-level-console=info \
backup
runner 在前后读取 JSON catalog,要求恰好新增一份:
type=full
error=false
stanza status code=0
正式结果:
label 20260729-201041F
backup command 2085.526 ms
logical bytes 36,121,841
repository delta 4,539,288
backup archive timeline 7
不公开 repository key/cipher pass。
restore point
runner:
SELECT pg_create_restore_point(
'pg36_ch21_run_20260729T201040Z_961665aa_keep'
);
具体名称由 run 生成,不应手工照抄。evidence 保存:
name
LSN
WAL segment
created_at UTC
强制并检查 archive
然后:
sudo -iu postgres \
pgbackrest --stanza=pg-test --log-level-console=info check
注意没有 --repo=1。正式成功:
check duration 598.471 ms
target WAL ...000020
repository max WAL ...000021
target_wal_covered true
公开结果可以省略完整 WAL 名;完整 secret-safe evidence 保留以审计。
最初 runner 对 check 传了:
pgBackRest 2.59.0 返回:
ERROR [031]: option 'repo' not valid for command 'check'
失败发生在 restore path 创建前:
backup succeeded
markers retained
no isolated directory
no postmaster
source healthy
我们没有删除失败 backup/marker 来伪装“一次通过”,而是修正 command-specific
option,再做一轮 fresh formal evidence。
只在 exact local sandbox:
export PG36_CH19_INVENTORY=/absolute/mode-0600/reviewed.yml
export PG36_EVIDENCE_DIR=/absolute/new-empty/ch21-run
export PG36_CH21_TARGET=pg36-l2-vagrant/pg-test
export PG36_CH21_NONPRODUCTION=true
export PG36_CH21_PRODUCTION_DATA=false
export PG36_CH21_PRODUCTION_TRAFFIC=false
export PG36_CH21_CONFIRM=BACKUP_AND_ISOLATED_PITR_CH21
static/labs/ch21/task.sh drill:pitr
evidence dir 非空则拒绝覆盖。
21.6.2 恢复到隔离集群,核对数据库与业务不变量
restore root
formal:
/data/pg36-ch21-restore/
run_20260729T201040Z_961665aa/
data/
socket/
log/
spool/
pg_hba.restore.conf
preflight:
root exact allowlist
root does not exist
not symlink
port 55432 unused
owner postgres
mode 0700
已存在 path 不做 --delta 覆盖,直接拒绝。
exact backup 与 target
formal restore 绑定:
--set=20260729-201041F
--type=name
--target=<this run keep restore point>
--target-action=promote
--target-timeline=latest
--archive-mode=off
这同时固定:
which base
where replay stops
what happens at target
which timeline path
whether recovered fork can archive
文件恢复
结果:
restore_copy_ms = 2758.206
files/bytes from exact backup catalog
recovery.signal present
standby.signal absent
copy 完成不算 PostgreSQL 恢复完成。
隔离启动
runner 用 exact PostgreSQL 18 pg_ctl,覆盖:
listen_addresses=''
port=55432
private Unix socket mode=0700
private HBA
ssl=off
archive_mode=off
primary_conninfo=''
primary_slot_name=''
shared_preload_libraries=''
logging_collector=off
cluster_name=pg36-ch21-restore
live replica:
/pg/data
port 5432
cluster_name=pg-test
Patroni streaming
两者路径与控制面不相交。
recovery-critical maxima
source 观测:
max_connections=500
max_worker_processes=24
max_wal_senders=50
max_prepared_transactions=0
max_locks_per_transaction=500
formal restore 携带这些值。
开发时曾尝试:
PostgreSQL 在重放前 FATAL 拒绝。修复方法不是降低 source 或编辑控制文件,
而是让隔离 runtime 满足 source WAL 记录的要求。
两个 readiness 时刻
第一时刻:
start -> first connection = 962.980 ms
pg_is_in_recovery() = true
transaction_read_only = true
第二时刻:
start -> promoted = 1318.876 ms
pg_is_in_recovery() = false
transaction_read_only = false
差:
所以:
pg_ctl -w returned
!= target-action promotion complete
rollback write probe
promotion 后:
BEGIN;
CREATE TEMPORARY TABLE pg36_ch21_writable_probe(value integer);
INSERT INTO pg36_ch21_writable_probe VALUES (1);
ROLLBACK;
结果:
它证明当前连接可写,不给历史副本增加持久业务事实。
business boundary
exact run:
base present true
keep present true
discard present false
unexpected stages 0
如果只看 row count=2,仍可能误拿其他 run;runner 对 exact token 比较。
lineage
system identifier relation = matches source
raw identifier recorded = false
source timeline = 7
restored timeline = 8
timeline increment = 1
这是同一物理血缘的一次合法 PITR fork。
isolation proof
运行中:
TCP listener false
Unix socket true
socket mode 0700
socket owner postgres:postgres
postmaster.pid true
archive_mode off
Patroni managed false
仅把 port 改成 55432 不算隔离;若 listen_addresses=0.0.0.0,仍会向网络暴露。
停止
pg_ctl -D <exact-data> -w -t 30 -m fast stop
停止后:
postmaster.pid false
Unix socket false
TCP listener false
restore directory retained true
目录保留供审查,未自动删除。
source postflight
恢复后重新执行 chapter-19 gate,并在 drill 内检查 .13 live instance:
pg-test-1 leader/running
pg-test-2 replica/streaming
pg-test-3 replica/streaming
all Patroni timeline 7
source system ID unchanged
pg-test-3 in_recovery=true
pg-test-3 replay paused=false
pg-test-3 port=5432
replica lag=0 bytes
isolated timeline 8 不会加入 source Patroni cluster。
不做什么
no pgsql-pitr destructive replacement
no Patroni reinit
no failover/switchover
no /pg/data write
no service routing
no repository expire
no restore directory deletion
这使实验回答“backup 能否恢复”,而不是把多个高风险动作揉成一个结果。
21.6.3 输出 RPO/RTO 实测、证据链和失败处理 SOP
reference result
restore-run.json:
{
"backup_command_ms": 2085.525583,
"restore_copy_ms": 2758.205792,
"start_to_first_connection_ms": 962.980167,
"first_connection_in_recovery": true,
"start_to_promoted_ms": 1318.876208,
"first_connection_to_promoted_ms": 355.896041,
"base_present": true,
"keep_present": true,
"discard_present": false,
"source_timeline": 7,
"restored_timeline": 8,
"isolated_postmaster_stopped": true,
"production_ch21_gate": "pending"
}
它不含 credential 或 raw system ID。
这次 RPO 证明了什么
证明:
fresh full ended before keep
keep WAL and named point were archived
restore stopped at named point
post-target discard did not replay
可以说:
named-point selection for this run passed
不能说:
production RPO = 0
any time in 14 days is recoverable
worst-case archive delay is 0
region loss loses no data
因为实验主动 pg_switch_wal() 并 check,没有模拟 source 在 open segment
尚未归档时突然毁坏。
这次 RTO 测量了什么
已测:
backup command
repository check
file restore copy
start to first read-only
start to promotion
未测:
incident detection
human decision/approval
new host provisioning
cold object retrieval
large production bytes
full business validation
application configuration
DNS/proxy/client cutover
unknown transaction reconciliation
backlog clearing
所以:
2.758s+1.319s=RTOproduction甚至简单相加也不完整,因为时钟阶段与执行方式要明确。
四类时钟
建议输出:
T_backup
T_restore_copy
T_recovery_consistent/read_only
T_recovery_promoted
T_database_validation
T_business_validation
T_service_cutover
T_backlog_clear
本章前四项中的三个有正式观测,后四项保持未测。
evidence tree
<run>/
├── preflight-ch19/
├── drill/
│ ├── drill-manifest.json
│ ├── source-before.json
│ ├── fixture.json
│ ├── backup.json
│ ├── recovery.json
│ ├── isolated-shutdown.json
│ ├── source-after.json
│ ├── validation-report.json
│ └── negative-report.json
├── postflight-ch19/
└── review.txt
manifest 记录所有 source input SHA-256。restore-run.json 与
migration-effort.json 是 outcome,不参与输入 hash,但 reviewer 会把
published outcome 与 validation report 对齐。
十四个反例
negative-cases.json 要求拒绝:
formal:
case_count=14
actual_code == expected_code for every case
status=ok
为什么测反例
一个 validator 若只接受正确 evidence,可能只是检查文件存在。让每个关键
断言被单独破坏并得到预期错误码,证明 decision rule 真正约束:
positive proof
+ negative falsification
这比“脚本 exit 0”更有审计价值。
read-only 重验
formal 结束后:
PG36_EVIDENCE_DIR=/absolute/completed/ch21-run \
static/labs/ch21/task.sh all
输出:
status=validation-ok
sandbox_named_pitr=accepted-with-exceptions
counterexamples=14-rejected
status=review-ok
secret_values_exported=0
raw_system_identifiers_exported=0
isolated_postmaster=stopped
restore_directory=retained
production_ch21_gate=pending
mutation=none
它不会重跑 backup/restore。
失败 SOP:backup
backup command fails
-> preserve console/log exec-id
-> inspect stanza/repository/archive
-> check source and disk health
-> do not select partial label
-> retry only after cause and policy review
若已插入 base,它只是 synthetic evidence;不要为“干净”自动删。
失败 SOP:archive gap
target segment > repository max or actual restore requests missing WAL
-> stop
-> preserve source pg_wal
-> inspect spool/archive errors/capacity
-> recover missing WAL if possible
-> choose earlier target only with business approval
不能跳过 WAL 或把“最新可到达点”擅自当成批准 target。
失败 SOP:restore/start
restore fails
-> retain exact directory/log
-> ensure no postmaster remains
-> inspect permission/tablespace/key/WAL
-> create a new run/path for retry
不要用 --delta 在不清楚内容的目录上反复覆盖。
失败 SOP:target mismatch
base/keep missing or discard present
-> reject candidate
-> do not expose service
-> review target/backup/inclusive/timeline
-> select a new target with data owner
-> restore to another fresh path
PostgreSQL 能启动不降低严重性。
失败 SOP:isolation
若发现:
TCP listener
archive_mode=on
wrong HBA
Patroni/DCS membership
path overlaps /pg/data
立即停止 isolated instance,保留证据,确认 source repository 和 routing
未污染,再调查。不要“验证完再关”。
reset
删除不是 acceptance 的一部分。单独入口要求:
exact target
nonproduction guards
exact run ID
PG36_CH21_RESET_CONFIRM=DELETE_ONE_CH21_SANDBOX_RUN
no postmaster.pid
no Unix socket
path exact allowlist
not symlink
然后只删除一个 retained directory 与对应 run rows。本文 formal 没有执行
reset。
生产 gate
沙箱通过后,生产仍需:
- 代表性生产规模与 change/WAL rate;
- 独立 production-class restore infrastructure;
- immutable、off-site、独立 credential/key 证明;
- 任意 time/XID/LSN 与 inclusive 语义;
- 最旧 retention target;
- missing WAL、lost key、repository outage;
- extension/tablespace/config/PKI 完整性;
- 应用与外部系统不变量;
- service cutover、client outcome 与 backlog;
- region loss 与 break-glass access;
- 多次样本分布,而不是一次秒数;
- 生产 change/incident authority。
migration-effort.json 把这些保持为
required_next_evidence。
本章最终判定
sandbox named PITR
accepted with ten explicit exceptions
PostgreSQL physical lineage
matched
target boundary
base + keep, no discard
isolation
path/process/socket/archive isolated
shared restore host exception remains
source safety
healthy before and after
production
pending
小结
一次可信恢复演练同时具备:
known business boundary
+ fresh or exactly selected backup
+ continuous WAL evidence
+ isolated target
+ two-phase readiness
+ lineage/timeline proof
+ positive and negative invariants
+ source postflight
+ shutdown
+ residual-risk ledger
这才把“我们有备份”升级为“我们曾在明确边界内证明它可以恢复”。
下一章继续服务面:当一个 PostgreSQL 实例真的可写之后,连接池、代理、
路由、会话与客户端重试如何决定用户何时恢复。
上一节:用 pgBackRest 交付备份策略 · 返回本章目录 · 下一章:四通八达:服务接入、连接池与路由 ·
查看全书目录 · 查看索引中心