PostgreSQL 恢复演练:误删、PITR 与 WAL 缺失验证¶
30 秒版(开场)¶
备份可用性要通过恢复证明:校验 base backup、连续归档 WAL,并把独立实例恢复到明确业务边界,再核对资金分录和余额。本例在 PostgreSQL 17 中先备份、再过账、创建命名恢复点、追加目标外交易并误删,验证恢复点前事实保留、之后事实消失;再移除必需 WAL,验证恢复必须失败。数据库能连接不等于已到达恢复目标。
3 分钟版(精讲深度)¶
- 准备证据链:物理 base backup、备份清单校验、连续 WAL、timeline、目标点和配置;逻辑 pg_dump 不能直接用于 WAL 物理恢复。
- 在独立位置恢复:保留原故障库,恢复目录独立,配置
restore_command、recovery.signal和目标;确认到达目标并退出 recovery 后再验收。 - 验收业务而非进程:目标前 post-backup journal 必须存在,目标后 journal 必须不存在,账户余额应等于期初加分录;缺 WAL 不能降级成“尽可能启动即可”。
10 分钟版(原理 + 图示)¶
flowchart LR
B[Base backup + manifest] --> Restore[独立恢复目录]
W[连续归档 WAL + timeline] --> Restore
Target[命名目标 before_delete] --> Restore
Restore --> Reached{到达目标并完成恢复?}
Reached -->|否| Reject[拒绝验收 / 保留诊断]
Reached -->|是| Check[检查 journal / 余额 / 目标外交易]
Check --> Accept[制定写入接管与外部对账方案]
本例时间线¶
| 顺序 | 操作 | 验收意义 |
|---|---|---|
| 1 | 写入 restore-kept,执行 pg_basebackup -X stream |
建立物理备份;pg_verifybackup 检查清单 |
| 2 | 切 WAL,写入 after-base |
强制证明后续 WAL 重放,而非只恢复备份文件 |
| 3 | 创建 before_delete 恢复点 |
目标在误删前,有明确 WAL 位置 |
| 4 | 写 after-target,管理员 TRUNCATE journal/entries/outbox |
制造目标后的合法提交与误删 |
| 5 | 切 WAL,等待目标段归档 | 目标不能只存在源机未归档 WAL 中 |
| 6 | 从 base + archive 恢复新实例 | 核对目标前后业务事实与余额 |
| 7 | 复制归档,删除目标所在必需段,再恢复另一目录 | 未到达目标必须报错,不能成为可写“成功恢复”实例 |
示例使用命名 restore point,降低墙钟时间、时区和事务边界歧义。事故往往没有预置恢复点, 需要根据审计、提交时间、transaction ID 或 LSN 确定目标,明确 inclusive 语义,并先在隔离副本核验。 恢复点是 WAL 标记,不是业务表中的一行记录,也不是逻辑备份。
恢复配置固定 PostgreSQL 17:
restore_command = 'cp /lab/archive/%f %p'
recovery_target_name = 'before_delete'
recovery_target_timeline = 'current'
recovery_target_action = 'promote'
脚本写入 recovery.signal,清除恢复副本中备份附带的 WAL 文件,确保测试从归档获取必需日志。
current 用于本实验单 timeline 场景;生产有 failover/timeline 分叉时,必须选择正确历史和目标分支,
不能机械复制这一项。归档中不存在某个探测文件可能正常,不能仅看一次 cp 失败判定恢复失败;
负例检查的是 PostgreSQL 明确报告未到达指定恢复目标,且实例无法继续提供可写服务。
生产场景¶
- 误删表、错误批量扣款、损坏投影:先停写与证据保全,在独立实例恢复,审查应保留的后续合法业务。
- 定期验证备份清单、归档连续性、恢复时长、目标可达性和账本一致性;副本会复制误删,不能代替备份。
- 恢复数据库不会撤销已经发出的付款、链上交易或已消费消息。接管前必须核对外部事实、Inbox/Outbox 与消费水位,防止旧快照重发副作用。
- 归档需异故障域、访问控制、不可变保留与容量告警。本例同容器目录只便于验证恢复机制,没有灾备隔离能力。
排查与工具¶
python3 examples/postgres-reliability/verify.py --only recovery \
--report /tmp/knowledge-recovery.json
自动检查:base manifest 可验证;恢复后不处于 recovery;journal 数与目标前一致;after-base 保留;
after-target 不存在;Bob 余额一致;balance_drift 为零。缺 WAL 负例要观察到
recovery ended before configured recovery target was reached,并检查连接不能成功。
输出 recovery_seconds 只表示这次小型 fixture 的恢复启动与检查阶段耗时,不包括故障发现、
备份下载、审批、外部对账或业务切流,不是生产 RTO 承诺。报告同时记录真实 PostgreSQL 版本与本地镜像 ID。
架构取舍¶
| 手段 | 适用 | 边界 |
|---|---|---|
| 逻辑备份 | 表级导出、选择性迁移 | 不能直接结合物理 WAL 做 PITR |
| 物理备份 + WAL | 整个 cluster 恢复到目标 | 需完整日志与兼容版本,不能只还原某张表 |
| 只恢复派生投影 | 原事实可靠、仅读模型损坏 | 不能修复丢失或错误的资金事实 |
| HA replica | 主机故障降低中断 | 会同步错误写入,不代替隔离备份 |
深挖问答¶
- 为什么 pg_verifybackup 通过仍不够? → 它不能代替实际恢复和业务核对,目标之后需要的归档 WAL 仍可能缺失。
- 为什么不直接恢复覆盖原库? → 会损失取证材料,也无法比较误删后的合法交易;先隔离恢复再决定接管或补数据。
- 为什么数据库 ready 还不能验收? → hot standby 在恢复过程中也能接受查询,要确认目标到达、恢复状态和业务边界。
- 缺 WAL 可以跳过继续吗? → 不能跳过必需 WAL 并声称达到指定目标,应修复归档或选择另一个已核验恢复目标。
- 恢复后直接启动 relay 可以吗? → 不行。旧库的 published/inbox 水位可能落后真实外部副作用,必须先对账与隔离执行。
反模式与事故¶
- 只检查备份上传成功,不做恢复或业务查询。
- 将生产 archive 直接用于破坏性测试,或删除唯一归档副本。
- 把 pg_dump 当成 pg_basebackup,或者拿新主机的错误 timeline 恢复旧目标。
- 启动成功就立即切流,忽略支付渠道、链上执行和 Broker 水位。
证据边界与相关链接¶
演练只创建唯一名称容器和其中独立目录,未连接现有数据库,也不发布宿主机端口;结束后清理该次容器与匿名卷。 它实际执行 PostgreSQL 物理备份、WAL 重放、误删恢复与缺日志负例;不证明跨主机灾备、远程归档耐久性或生产 RPO/RTO。