恢复演练
再完善的备份策略,如果没有经过真实恢复的检验,都只停留在“看起来应该有备份”的层面。恢复演练的价值在于:把平时写在文档里的步骤,在一个安全的环境里真正跑一遍,用实测数据回答几个平时难以回答的问题——我们到底多久能恢复?恢复出来的数据对不对?步骤里有没有漏掉的环节?
演练最容易走入两种极端:一种是从不做演练,理由是“太耗时、影响生产”;另一种是做成了形式化的 PPT 会议,大家对着流程图讨论一遍就算完成。真正有效的演练应当是动手跑的:拿一份真实的备份、在一个独立的实例上恢复、按流程验证、把每一步的耗时记下来,最后输出一份可以用来改进的结论。
这一节围绕“怎么把演练做成一次有价值的工程活动”展开:设计什么场景、用什么环境、测哪些指标、怎么验证结果、怎么把结论落成改进。
前置知识
建议先阅读:异地备份策略
基础概念
什么是恢复演练
恢复演练是一次受控的、以检验恢复能力为目标的实战操作。它区别于日常的备份成功检查,关键在于三个限定:
- 受控:在隔离环境中进行,不影响生产;有明确的开始与结束边界。
- 以恢复为目标:核心动作是从备份恢复出可用实例,而不是“检查备份文件存在”。
- 有输出:产出可测量的结果(耗时、失败点、结论),而非“跑了一遍没问题”。
缺了任何一条,演练的价值都会打折。只检查文件不恢复,测不出恢复能力;在生产上做,测出的结果不可信(因为不会真的停生产);没有输出,下次还是不知道改什么。
演练要回答的四个问题
一次合格的演练应当能明确回答下面四个问题:
| 问题 | 对应指标 | 怎么回答 |
|---|---|---|
| 我们能不能恢复 | 可恢复性 | 是否成功恢复并启动 |
| 我们多久能恢复 | RTO 实测值 | 全程耗时测量 |
| 我们丢了多长时间的数据 | RPO 实测值 | 恢复点与故障时刻之差 |
| 恢复出来的数据对吗 | 数据正确性 | 业务校验 / 一致性检查 |
只回答“能不能”,就只能得到“能”或“不能”,无法回答管理层最关心的“要多久”。因此演练必须带计时。
桌面演练、组件演练与全流程演练
按投入与深度,演练通常分为三档:
| 类型 | 形式 | 耗时 | 覆盖面 | 适用 |
|---|---|---|---|---|
| 桌面演练 | 会议室里按脚本推演,不实际操作 | 1~2 小时 | 决策、沟通、职责 | 新流程上线前、人员变动后 |
| 组件演练 | 只验证某一环节,如“恢复单个备份” | 数小时 | 技术步骤 | 频繁进行,覆盖各技术环节 |
| 全流程演练 | 从故障发现到业务恢复全部跑完 | 半天到数天 | 全链路 | 每季度或每半年一次 |
三档不是替代关系,而是组合关系:桌面演练练兵“决策与协作”,组件演练保证“每个零件都好用”,全流程演练检验“装在一起能不能跑”。
演练环境应当是隔离的
演练必须在与生产隔离的环境中进行,理由有三个:
- 安全:演练包含恢复启动、提升、可能的重命名等动作,误操作生产后果严重。
- 可信:只有在不影响生产的前提下,才敢真正执行长时间停止、切换等步骤。
- 可重复:隔离环境可以随时重建,允许反复试错。
隔离不一定要用最贵的方式实现,常见做法:
- 用与生产同版本但规模较小的独立实例;
- 用容器快速起一个临时实例;
- 用云上按需创建、演练后销毁的实例;
- 用虚拟机快照,演练完回滚。
关键点是:演练环境的 PostgreSQL 大版本要与生产一致,否则恢复行为与参数支持可能有差异。
演练的成功判据
“演练成功”必须有明确定义,否则各人理解不同。建议从四个维度定义判据:
| 维度 | 判据示例 |
|---|---|
| 技术成功 | 实例正常启动,未处于恢复状态,接受连接 |
| 数据正确 | 关键表行数、关键业务查询结果与预期一致 |
| 时间达标 | 实测 RTO 小于目标 RTO |
| 流程可用 | 无“卡住需要临时翻文档”的关键步骤 |
四条都满足才算成功。技术成功但超时,说明 RTO 目标不现实或恢复方式需要优化;技术成功但流程卡顿,说明文档或工具需要改进。
RTO 与 RPO 的实测
演练与日常监控最大的区别,是能给出实测值而不是理论值:
- RTO 实测:从“假设故障发生”计时,到“恢复出来的实例可以对外提供服务”停止计时。这个口径必须在演练前统一,否则各人计出的数字无法比较。
- RPO 实测:用恢复目标点的 LSN 或时间,与实际灾难时刻(或演练设定的故障时刻)比较,得出实际丢失的数据量或时间。
这两个数是演练最有价值的产出,也是向上汇报时最容易被理解的指标。
计时的口径要统一
计时口径不统一会让演练结果失去可比性。建议在演练前明确:
- 起点:从“故障被发现”还是“从决定启动恢复”开始算?两者差值可能是整个沟通成本。
- 终点:到“实例可连接”还是“业务校验通过”还是“流量切换完成”?
- 包含项:是否包含下载备份、等待相关人员到场、审批时间?
推荐做法是记录整条时间线的分段耗时,而不是只记一个总数。分段数据才能指出瓶颈在哪。
演练频率
演练频率取决于业务重要性与变化速度。一个常用参考:
| 业务等级 | 全流程演练 | 组件演练 | 桌面演练 |
|---|---|---|---|
| 核心(不允许长时间中断) | 每季度 | 每月 | 每半年 |
| 重要(可短时中断) | 每半年 | 每季度 | 每半年 |
| 一般(可较长中断) | 每年 | 每半年 | 每年 |
此外,以下时机应当额外安排演练:
- 数据库大版本升级后;
- 备份方式或存储发生变更后;
- 关键人员变动后;
- 组织结构或流程重大调整后;
- 真实灾难恢复之后(用真实事件做一次复盘式演练)。
演练的组织与角色
即使是小团队,也应当在演练中区分角色,否则容易变成“一个人全程操作,其他人围观”:
| 角色 | 职责 |
|---|---|
| 指挥 | 决定何时进入下一步、是否中止演练 |
| 操作 | 执行具体命令 |
| 记录 | 记录每一步时间点、命令与结果 |
| 验证 | 独立验证数据与可用性 |
| 观察 | 不参与操作,专门发现问题(可选) |
记录与验证必须由非操作者承担,这样才可能发现操作者自己忽略的问题。
演练记录与可追溯
演练的产出文档应当能被后来者直接复用。一份记录至少包含:
- 演练时间、参与人、角色分工;
- 演练场景与假设的故障;
- 使用的备份来源与时间点;
- 每一步的时间戳、执行的命令、观察到的结果;
- 遇到的问题与当时的处理方式;
- 实测 RTO、RPO;
- 结论与改进项(含负责人与截止时间)。
没有记录的演练等于没做——下次还得从零开始摸索。
演练与真实灾难的差距
必须承认演练无法完全模拟真实灾难。主要差距:
| 方面 | 演练 | 真实灾难 |
|---|---|---|
| 信息 | 通常已知故障类型与范围 | 信息不全,需要逐步排查 |
| 压力 | 可控,随时可暂停 | 业务压力持续,无法暂停 |
| 环境 | 隔离环境,资源可用 | 可能同时缺少主机、网络、人手 |
| 决策 | 有明确的“演练目标” | 需要在不确定中做取舍 |
| 备份有效性 | 通常已提前抽查 | 灾难当下才知道是否可用 |
正因为有这些差距,演练设计应当刻意引入不确定性:不提前告知具体故障场景、只给出有限信息、要求真实执行切换,而不是“看一遍命令”。
语法与用法
准备隔离的演练实例
演练通常从准备一份数据目录开始。先从备份恢复出一个目录:
# 假设已有一份 plain 格式的基础备份
cp -a /srv/offsite_backup/base_20260920 /var/lib/pgsql/drill_data注意 cp -a 保留权限与属主;恢复出来的目录属主应当是运行 PostgreSQL 的操作系统用户。
校验演练用的备份
恢复前先校验备份完整性,避免用一份坏备份做演练、白费时间:
pg_verifybackup -n /var/lib/pgsql/drill_data-n 跳过 WAL 可解析性检查,加快速度;如果演练目的包含验证 WAL,则应去掉 -n。
配置恢复并启动
在演练数据目录中配置恢复参数并放置信号文件:
# /var/lib/pgsql/drill_data/postgresql.conf
restore_command = 'cp /srv/offsite_wal/%f %p'
recovery_target_time = '2026-09-20 10:30:00+08'
recovery_target_inclusive = on
recovery_target_action = 'pause'touch /var/lib/pgsql/drill_data/recovery.signal
pg_ctl -D /var/lib/pgsql/drill_data startrecovery_target_action = 'pause' 让实例在到达目标点后暂停,便于先验证数据再决定是否继续。
在恢复目标点暂停与继续
当恢复到目标点并因 pause 停下时,可以用恢复控制函数继续或提升:
-- 确认当前仍在恢复中
SELECT pg_is_in_recovery();
-- 查看已重放到的位置
SELECT pg_last_wal_replay_lsn(), pg_last_xact_replay_timestamp();-- 继续重放(越过恢复目标点)
SELECT pg_wal_replay_resume();pg_wal_replay_pause() 与 pg_wal_replay_resume() 用于恢复期间的暂停与继续,只有在恢复中才可用。
检查恢复状态
演练中需要频繁确认实例处于什么状态:
-- 是否仍在恢复
SELECT pg_is_in_recovery();
-- 已接收与已重放的 WAL 位置
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();
-- 最后一次重放事务的时间戳
SELECT pg_last_xact_replay_timestamp();# 从外部检查实例是否接受连接
pg_isready -h 127.0.0.1 -p 5433127.0.0.1:5433 - accepting connections提升演练实例
恢复完成后,把实例提升为可写状态:
pg_ctl -D /var/lib/pgsql/drill_data promoteserver promoting也可以从 SQL 侧提升:
SELECT pg_promote();提升后可以用 pg_is_in_recovery() 确认已不再处于恢复状态。
校验恢复出来的数据
提升后做数据校验。校验内容应当与业务相关,至少覆盖:
-- 实例状态
SELECT pg_is_in_recovery() AS still_in_recovery;
-- 关键表行数(表名按实际业务替换)
SELECT count(*) FROM orders;
-- 最新记录的时间戳,判断数据新鲜度(列名按实际业务替换)
SELECT max(created_at) FROM orders;
-- 数据库列表与大小
SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY pg_database_size(datname) DESC;行数、最大时间戳这两类校验最简单也最有效:行数异常说明恢复不完整,最大时间戳异常说明恢复点不对。
检查数据目录的页校验和
如果启用了数据页校验和,可以在实例关闭状态下检查整目录:
pg_checksums -c -D /var/lib/pgsql/drill_dataChecksum operation completed
Files scanned: 1234
Blocks scanned: 567890
Bad checksums: 0
Data checksum version: 1Bad checksums: 0 表示没有检出损坏块。该工具需要在实例停止时运行。
查看控制文件信息
pg_controldata 会输出控制文件中的关键信息,适合用来快速判断实例状态与恢复进度:
pg_controldata /var/lib/pgsql/drill_datapg_control version number: 1300
Latest checkpoint location: 0/1A0000A8
Latest checkpoint's TimeLineID: 3演练中可以用它确认时间线是否正确、检查点位置是否与预期一致。
查看 WAL 文件内容
需要确认某个 WAL 段里到底有哪些记录时,可以用 pg_waldump:
pg_waldump /srv/offsite_wal/0000000300000014000000A5 | head -n 20输出包含每条 WAL 记录的类型与位置,适合排查“为什么恢复停在了某个位置”。
演练中记录时间线
建议在演练中用统一格式记录每一步:
[2026-09-20 14:00:00] 开始计时(演练开始)
[2026-09-20 14:02:30] 确认实例损坏,决定启动恢复
[2026-09-20 14:05:10] 开始校验异地备份
[2026-09-20 14:31:40] 校验通过,开始复制数据目录
[2026-09-20 15:12:00] 数据目录就绪,写入恢复配置
[2026-09-20 15:15:20] 启动实例,开始 WAL 重放
[2026-09-20 15:48:05] 到达恢复目标点,暂停
[2026-09-20 16:02:00] 数据校验通过
[2026-09-20 16:05:30] 提升完成,接受连接
[2026-09-20 16:06:00] 结束计时这样得到的不只是一个总时长,而是每个阶段的耗时分布。
清理演练环境
演练结束后按顺序清理,避免占用生产资源:
# 停止演练实例
pg_ctl -D /var/lib/pgsql/drill_data stop
# 确认已停止后删除目录
ls /var/lib/pgsql/drill_data
rm -rf /var/lib/pgsql/drill_data清理前应当先归档演练记录,避免把有价值的日志一起删掉。
详细说明
演练场景的设计
演练场景应当覆盖“真实可能发生的故障类型”,而不是只挑最容易的那种。一个有效的场景集合至少包含五类(下面逐个展开):
- 逻辑损坏类:误删表、错误批处理;
- 实例损坏类:数据目录损坏、实例无法启动;
- 机房级故障:整机房不可用;
- 恶意破坏类:勒索软件;
- 备份本身出问题:备份文件损坏或缺失。
每类场景的恢复路径与难点不同,只演练其中一类无法覆盖真实风险。
场景一:误删表
设定:某次发布脚本错误执行了 DROP TABLE,需要把表恢复到删除前。
要点:
- 恢复目标点必须早于删除动作,需要先定位删除的时间或 LSN。
- 常见做法不是整库回滚,而是恢复出一个临时实例,从里面把表数据导出再导回生产。
- 演练重点在于“怎么找到那个时间点”以及“怎么只恢复一张表”。
典型卡点:没人知道删除发生在几点;或找到了时间点但没有当时的 WAL 归档。
场景二:实例完全损坏
设定:数据目录所在磁盘损坏,实例无法启动。
要点:
- 需要从基础备份 + WAL 归档完整重建。
- 演练重点是全程计时,因为这是最常见的“真灾难”。
- 应当演练一次“用最老的可用基础备份恢复”,检验 WAL 归档是否真的能覆盖到最新。
典型卡点:基础备份太旧导致 WAL 重放时间极长;或归档链中间缺段导致恢复中断。
场景三:机房不可用
设定:整个机房失去电力或网络,短期内无法恢复。
要点:
- 必须使用异地备份,且要演练数据回传这一步。
- 演练重点是“在另一处从零搭起一个可用实例”的完整流程,包括主机、存储、网络、DNS 切换。
- 这是最能暴露组织问题(谁负责开机器、谁负责改 DNS、谁批准)的场景。
典型卡点:备用环境不存在;回传时间远超预期;改 DNS 需要外部审批,演练时临时找人。
场景四:勒索软件
设定:生产库与本地备份目录同时被加密,只有异地的不可变副本幸存。
要点:
- 演练重点是只使用不可变/离线副本恢复,检验其可用性与保留期设置。
- 需要验证:异地副本确实未被影响、密钥可取得、恢复链路完整。
- 还应演练“恢复到一个干净环境”的过程,避免把受感染的系统引入新环境。
典型卡点:密钥与备份放在一起导致无法解密;不可变副本保留期过短已被删除。
场景五:备份文件损坏
设定:恢复时发现清单校验失败,或某个 WAL 段不可读。
要点:
- 演练重点是发现与回退:如果这份备份不可用,退回到哪一份?
- 需要检验“回退方案是否真的存在”,而不是文档上写着“使用上一份备份”。
- 这类演练常常会发现保留份数不足。
典型卡点:只有一份可用备份,且它坏了;或上一份备份太旧,回退后数据损失不可接受。
演练为何常流于形式
演练变成形式化,通常有这几个原因:
- 没有计时:只关注“跑通了”,不记录耗时,无法回答 RTO。
- 环境提前准备好:备份已解压、配置文件已写好,演练只剩“按回车”。
- 只有一个人参与:没有角色分工,测不出协作环节的问题。
- 场景提前告知:大家知道要做什么,跳过发现与判断阶段。
- 没有结论落地:发现问题但没人跟进,下次依旧。
要突破形式化,最有效的两个改动是:强制执行计时,以及把环境准备纳入演练范围(从零开始,而不是从半成品开始)。
时间测量应当怎么记
建议把总时间拆成下面这些分段,逐个记录:
1. 发现(告警到有人响应)
2. 确认(响应到确认是灾难)
3. 决策(确认到决定启动恢复)
4. 准备(决定到环境可用)
5. 数据搬运(开始搬运到数据就绪)
6. 恢复启动(配置到实例启动)
7. WAL 重放(启动到到达目标点)
8. 校验(到达目标到校验通过)
9. 切换(校验通过到业务可写)分段记录的好处是能明确“改进哪一段收益最大”。多数团队的瓶颈不在 WAL 重放,而在决策与准备阶段。
演练中的常见卡点
从实践看,卡点很少出在技术命令本身,而集中在下面这些地方:
| 卡点 | 表现 | 改进方向 |
|---|---|---|
| 找不到备份 | 不知道哪份备份可用、在哪 | 维护备份清单与元数据 |
| 权限不足 | 恢复账号不能读备份或启动实例 | 提前配置并演练 |
| 配置遗漏 | 缺少 restore_command 或信号文件 | 用脚本化恢复,减少手工 |
| 密钥不可得 | 加密备份无法解密 | 密钥单独托管并演练取用 |
| 网络不通 | 回传链路上有防火墙 | 提前开通并记录 |
| 文档过时 | 按文档操作失败 | 演练后立即更新文档 |
| 人手不在 | 关键人休假 | 至少两人可执行 |
其中“找不到备份”和“配置遗漏”出现的频率远高于其他项。
恢复脚本应当版本化
把演练中验证过的恢复步骤整理成脚本,并纳入版本管理:
#!/usr/bin/env bash
# restore_drill.sh —— 演练用的最小恢复脚本骨架
set -euo pipefail
BACKUP_DIR="${1:?用法: restore_drill.sh <备份目录> <目标时间>}"
TARGET_TIME="${2:?缺少目标时间}"
DRILL_DATA="/var/lib/pgsql/drill_data"
WAL_ARCHIVE="/srv/offsite_wal"
cp -a "$BACKUP_DIR" "$DRILL_DATA"
cat >> "$DRILL_DATA/postgresql.conf" <<EOF
restore_command = 'cp ${WAL_ARCHIVE}/%f %p'
recovery_target_time = '${TARGET_TIME}'
recovery_target_inclusive = on
recovery_target_action = 'pause'
EOF
touch "$DRILL_DATA/recovery.signal"
pg_ctl -D "$DRILL_DATA" start脚本化的价值在于:把“靠记忆”变成“照脚本执行”,既降低门槛,也让演练可重复。
演练环境的网络与端口
演练实例通常与生产在同一台或同网段的机器上运行,因此必须避免端口冲突:
- 使用与生产不同的端口(例如生产 5432,演练 5433)。
- 使用不同的 Unix socket 目录与
unix_socket_directories。 - 避免与生产的归档、备份目录混用(演练脚本不要写到生产的归档路径)。
# 演练实例的端口设置
port = 5433
unix_socket_directories = '/tmp'一个常见的严重错误是:演练时误把 recovery.signal 或恢复配置写到生产数据目录,导致生产重启后进入恢复模式。
演练数据与生产隔离
演练环境的数据库可能包含生产数据(因为用的是真实备份)。因此:
- 演练环境的访问权限应当与生产同等严格,甚至更严。
- 演练结束后应当及时销毁实例与数据目录。
- 如果需要把数据交给更多人看,应当先做脱敏。
把生产备份在不受控的环境里长时间存在,本身就是一个数据泄露风险。
演练是否需要业务参与
需要,但参与程度可以分层次:
| 参与者 | 参与的演练类型 | 参与方式 |
|---|---|---|
| DBA / 运维 | 全部 | 操作与恢复执行 |
| 开发 | 组件演练、全流程演练 | 提供校验查询、判断数据正确性 |
| 业务 | 全流程演练 | 确认业务可用、决定是否接受数据损失 |
| 管理 | 桌面演练、全流程演练 | 决策与审批 |
只让技术团队参与的演练,无法检验“谁决定能否接受数据损失”这类关键环节,而这恰恰是真实灾难中最容易卡住的地方。
演练后的改进闭环
演练的价值最终体现在改进上。建议按固定格式输出结论:
演练日期: 2026-09-20
场景: 实例完全损坏(磁盘故障)
实测 RTO: 2 小时 6 分(目标 1 小时)→ 未达标
实测 RPO: 4 分 12 秒(目标 5 分钟)→ 达标
发现的问题:
1. 决策阶段耗时 38 分钟,缺少明确的授权人 → 责任人: 张三,10-15 前
2. 数据搬运耗时 40 分钟,回传带宽不足 → 责任人: 李四,10-31 前
3. 恢复文档中的归档路径与现网不一致 → 责任人: 王五,09-30 前
下次演练重点:
- 验证回传带宽提升效果
- 增加一次无预告的决策阶段演练每条问题都必须有责任人与截止时间,否则它会一直留在文档里。
演练与验证工具的关系
日常的自动化校验(如每次备份后跑 pg_verifybackup)和人工演练是互补关系:
| 手段 | 覆盖范围 | 频率 | 能发现什么 |
|---|---|---|---|
pg_verifybackup 自动校验 | 文件完整性 | 每次备份 | 文件损坏、缺失 |
| 自动恢复测试 | 恢复可启动性 | 定期自动 | 恢复链路是否可用 |
| 人工恢复演练 | 全流程(含决策与协作) | 定期人工 | 流程、协作、时间、人 |
前者保证“零件不会悄悄坏掉”,后者保证“整套流程在压力下能跑通”。不能互相替代。
演练与文档同步
演练是文档最好的检验方式:凡是需要临时翻文档才能往下走的地方,都是文档需要改进的地方。
建议在演练中专门安排一个人记录“在哪里卡住查文档”,演练结束后逐条修订。文档应当做到:
- 步骤顺序明确,不留“视情况而定”;
- 每条命令可复制执行(真实路径、真实参数);
- 关键判断点写明“怎么判断成功 / 失败”;
- 附上期望输出示例。
文档修订后,下一次演练应当能用更少的时间完成同样的流程,这就是可量化的改进。
演练的边界与风险
演练本身也有风险,需要提前控制:
- 资源风险:恢复大备份可能占满演练环境的磁盘,影响同机其他服务。
- 误操作风险:命令中的路径写错,可能覆盖生产数据。演练脚本应当对关键路径做前置检查。
- 网络风险:回传大量数据可能占满带宽,影响生产链路。
- 权限风险:演练用高权限账号,凭据管理需要与生产同等对待。
控制手段包括:在隔离环境执行、脚本中校验目标路径、限制带宽、演练时段避开业务高峰。
演练结果如何汇报
向上汇报时,最有说服力的是三个数字加一个趋势:
- 本次实测 RTO;
- 本次实测 RPO;
- 与目标值的差距;
- 与上一次演练相比的变化。
只报“演练成功”几乎没有信息量;报“RTO 从上季度的 3 小时降到 2 小时 6 分,仍高于 1 小时目标,主要瓶颈在决策阶段”才能推动资源投入。
从演练失败中学到更多
演练不达标不是坏事,反而是它的主要价值。一个每次都能“顺利通过”的演练,通常说明标准定得太松或者环境准备得太充分。
建议在演练中主动设置“意外”:例如临时宣布某个备份不可用、某位关键人缺席、网络限速。这样检验到的才是流程的韧性,而不是某个人的熟练度。
演练的最小可行版本
如果当前完全没有演练机制,不必一步到位做全流程演练。可以从最小可行版本开始:
- 每周自动把最新备份恢复到一个临时实例,验证能启动;
- 每季度做一次人工组件演练,实测一次恢复耗时;
- 每半年做一次带业务参与的全流程演练。
先有“能启动”的自动化验证,再有“计时”的人工演练,最后补齐协作与决策环节。这样投入低,但能持续积累可信的恢复能力证据。
实战示例
示例一:准备一份带计时的演练记录表
阶段 开始时间 结束时间 耗时
发现 2026-09-20 14:00:00 2026-09-20 14:02:30 2 分 30 秒
确认 2026-09-20 14:02:30 2026-09-20 14:05:10 2 分 40 秒
决策 2026-09-20 14:05:10 2026-09-20 14:43:00 37 分 50 秒
准备 2026-09-20 14:43:00 2026-09-20 14:45:00 2 分 00 秒
数据搬运 2026-09-20 14:45:00 2026-09-20 15:12:00 27 分 00 秒
恢复启动 2026-09-20 15:12:00 2026-09-20 15:15:20 3 分 20 秒
WAL 重放 2026-09-20 15:15:20 2026-09-20 15:48:05 32 分 45 秒
校验 2026-09-20 15:48:05 2026-09-20 16:02:00 13 分 55 秒
切换 2026-09-20 16:02:00 2026-09-20 16:06:00 4 分 00 秒
总计 2 小时 6 分 00 秒这张表能立刻指出瓶颈——本例中决策占 37 分钟。
示例二:从备份恢复到隔离实例
# 1. 复制备份到演练目录
cp -a /srv/offsite_backup/base_20260920 /var/lib/pgsql/drill_data
# 2. 校验备份
pg_verifybackup -n /var/lib/pgsql/drill_data
# 3. 写入恢复配置
cat >> /var/lib/pgsql/drill_data/postgresql.conf <<'EOF'
port = 5433
restore_command = 'cp /srv/offsite_wal/%f %p'
recovery_target_time = '2026-09-20 15:00:00+08'
recovery_target_action = 'pause'
EOF
# 4. 放置信号文件并启动
touch /var/lib/pgsql/drill_data/recovery.signal
pg_ctl -D /var/lib/pgsql/drill_data start示例三:确认恢复到目标点后暂停
-- 仍在恢复中,说明已停在目标点(pause 生效)
SELECT pg_is_in_recovery() AS still_in_recovery,
pg_last_wal_replay_lsn() AS replay_lsn,
pg_last_xact_replay_timestamp() AS last_replay_ts; still_in_recovery | replay_lsn | last_replay_ts
-------------------+-------------+------------------------
t | 0/2F1A3B88 | 2026-09-20 14:59:58+08last_replay_ts 略早于目标时间,符合预期。
示例四:越过恢复目标点继续重放
-- 继续重放到 WAL 末尾(仅在恢复中可用)
SELECT pg_wal_replay_resume();示例五:验证恢复出来的数据
-- 检查是否仍在恢复
SELECT pg_is_in_recovery();
-- 关键表行数
SELECT count(*) AS order_count FROM orders;
-- 最新数据时间
SELECT max(created_at) AS latest_order FROM orders; order_count
-------------
128437
latest_order
------------------------
2026-09-20 14:58:12+08最大时间戳早于演练设定的目标时间,说明恢复了预期范围内的数据。
示例六:提升演练实例并确认可写
pg_ctl -D /var/lib/pgsql/drill_data promote-- 提升后应为 f
SELECT pg_is_in_recovery();
-- 验证可写(在临时表上做,避免污染数据)
CREATE TEMP TABLE drill_write_test(id int);
INSERT INTO drill_write_test VALUES (1);
SELECT count(*) FROM drill_write_test;示例七:用 pg_checksums 检查数据页
# 需先停止实例
pg_ctl -D /var/lib/pgsql/drill_data stop
pg_checksums -c -D /var/lib/pgsql/drill_dataChecksum operation completed
Files scanned: 1234
Blocks scanned: 567890
Bad checksums: 0示例八:用 pg_controldata 核对时间线
pg_controldata /var/lib/pgsql/drill_data | grep -i -E 'timeline|checkpoint location'Latest checkpoint location: 0/2F1A3B88
Latest checkpoint's TimeLineID: 3时间线应与恢复时使用的 recovery_target_timeline 一致。
示例九:排查恢复停住的 WAL 位置
# 用当前重放位置定位 WAL 段
pg_waldump /srv/offsite_wal/000000030000002F00000001 | tail -n 5rmgr: Heap len (rec/tot): 54/ 1234, tx: 8841, lsn: 0/2F1A2F00, ...如果这里报错“无法打开文件”,说明归档目录中缺少该段 WAL,恢复链存在断点。
示例十:记录演练结论
演练日期: 2026-09-20
场景: 实例完全损坏
备份来源: s3://pg-offsite-backup/base/20260920/(时间线 3)
实测 RTO: 2 小时 6 分(目标 1 小时)→ 未达标
实测 RPO: 1 分 45 秒(目标 5 分钟)→ 达标
主要瓶颈: 决策阶段 37 分 50 秒
改进项:
1. 明确恢复启动的授权人,减少决策等待(责任人:张三,2026-10-15)
2. 提升异地回传带宽,缩短数据搬运时间(责任人:李四,2026-10-31)
3. 将恢复步骤脚本化,减少配置遗漏(责任人:王五,2026-09-30)示例十一:一次自动化的“能启动”验证
#!/usr/bin/env bash
# 最小验证:恢复最新备份到临时目录并确认能启动,随后清理
set -euo pipefail
LATEST_BACKUP=$(ls -dt /srv/offsite_backup/base_* | head -n 1)
TMP_DATA=$(mktemp -d /tmp/drill_XXXXXX)
cp -a "$LATEST_BACKUP"/. "$TMP_DATA"/
cat >> "$TMP_DATA/postgresql.conf" <<EOF
port = 5599
unix_socket_directories = '/tmp'
restore_command = 'cp /srv/offsite_wal/%f %p'
recovery_target_action = 'promote'
EOF
touch "$TMP_DATA/recovery.signal"
pg_ctl -D "$TMP_DATA" -w start
pg_isready -h 127.0.0.1 -p 5599
pg_ctl -D "$TMP_DATA" -w stop
rm -rf "$TMP_DATA"这个脚本适合放进定时任务,作为“备份可恢复性”的日常哨兵。
示例十二:演练后清理环境
# 停止演练实例
pg_ctl -D /var/lib/pgsql/drill_data stop
# 确认目录不再被使用
lsof +D /var/lib/pgsql/drill_data 2>/dev/null || true
# 删除演练数据
rm -rf /var/lib/pgsql/drill_data
# 确认归档目录未被演练写入
ls -lt /srv/offsite_wal | head -n 5注意事项
- 演练必须在隔离环境进行,误把恢复配置写入生产数据目录会导致生产进入恢复模式。
- 演练环境的大版本要与生产一致,否则恢复行为与参数支持可能有差异。
- 必须计时,没有耗时的演练无法回答 RTO,也无法推动改进。
- 计时口径要事先统一,否则不同次的演练结果不可比较。
- 不要提前准备好环境,把“准备阶段”纳入演练范围才有检验意义。
- 记录与验证应由非操作者承担,操作者容易忽略自己的问题。
- 演练实例要使用独立端口与 socket 目录,避免与生产冲突。
- 演练数据包含生产数据,权限与销毁都要受控,避免二次泄露。
- 不要把“命令跑通”当成演练成功,必须包含数据正确性校验。
- 加密备份的密钥取得过程必须演练,否则灾难当天可能无法解密。
- 演练结论必须落成带责任人与截止时间的改进项,否则问题会一直存在。
- 演练不达标是常态,重点在于差距是否在缩小,而不是是否“通过”。
最佳实践
- 建立三层演练体系:桌面演练练决策、组件演练验零件、全流程演练验整体。
- 每次演练都记录分段耗时,用数据定位瓶颈,而不是凭感觉。
- 统一 RTO / RPO 的计量口径,并在演练前向所有参与者明确。
- 把恢复步骤脚本化并纳入版本管理,降低对个人记忆的依赖。
- 每次备份后自动执行
pg_verifybackup,定期执行“能启动”的自动恢复验证。 - 演练场景覆盖逻辑损坏、实例损坏、机房故障、恶意破坏、备份损坏五类。
- 刻意引入不确定性(不预告场景、限定信息、临时制造意外),避免演练变成走过场。
- 让业务与管理方参与全流程演练,检验数据损失接受度与审批链路。
- 演练后立即更新恢复手册,把“需要翻文档才能继续”的地方补全。
- 每条改进项指定责任人与截止时间,并在下次演练中验证效果。
- 汇报时用“实测 RTO / 实测 RPO / 与目标差距 / 与上次对比”四个要素。
- 从最小可行版本起步(自动能启动验证 → 人工计时演练 → 全流程演练),持续迭代。
- 演练结束及时销毁隔离环境与数据,避免生产备份长期滞留在不受控位置。
- 把演练结果纳入备份策略的评审输入,用实测数据修正 RTO / RPO 目标。
下一节
继续学习:流复制概览
参考链接
- PostgreSQL 官方文档:Backup and Restore
- PostgreSQL 官方文档:Continuous Archiving and Point-in-Time Recovery (PITR)
- PostgreSQL 官方文档:Recovery Configuration
- PostgreSQL 官方文档:Recovery Control Functions
- PostgreSQL 官方文档:pg_verifybackup
- PostgreSQL 官方文档:pg_checksums
- PostgreSQL 官方文档:pg_controldata
- PostgreSQL 官方文档:pg_waldump
- PostgreSQL 官方文档:pg_ctl