回到事故前 30 秒
目标
真实执行一次误运行 DELETE 后恢复到事故发生前 30 秒。
环境
Pod 中已运行 PostgreSQL 16(5432,数据库 labdb,用户 lab,且为超级用户)。恢复副本会在同一个 Pod 中使用独立的 5433 端口运行。
export PATH=/usr/lib/postgresql/16/bin:$PATH
export PGDATA=/var/lib/postgresql/data
psql -h 127.0.0.1 -U lab -d labdb
工作目录
/root/work/wal 아카이브된 WAL
/root/work/base 베이스 백업 (원본 — 건드리지 않는다)
/root/work/restore 복구본 (base 를 복사해서 만든다)
步骤
archive_mode = on并重启pg_basebackup→/root/work/base- 写入两行数据并记录目标时间 →
03-target.txt - 执行
delete from ops_note+pg_switch_wal() - 创建
restore目录 +recovery.signal - 在 5433 启动并确认(应有两行)
- 与
pg_dump比较 →07-dump.txt - 总结 →
08-notes.md
必须遵守
- **不要在原始实例上恢复。**使用不同目录与不同端口。
- 在恢复副本配置中加入
archive_mode = off,否则恢复副本会覆盖原始实例的 archive。 - 第 4 步不要忘记
pg_switch_wal()。归档只复制已经写满的 segment,最后一次变更可能尚未切换出去。
启用 WAL 归档
启用 archive_mode,让 WAL 复制到 /root/work/wal。pg_stat_archiver.archived_count 必须大于 0。
lab 账号是超级用户。可执行 psql -h 127.0.0.1 -U lab -d postgres -c "alter system set archive_mode = on",并把 archive_command 设为 test ! -f /root/work/wal/%f && cp %p /root/work/wal/%f;其中 test ! -f 可防止覆盖。archive_mode 需要重启:pg_ctl -D /var/lib/postgresql/data restart -m fast -w。然后用 select pg_switch_wal() 切换一个 segment 进行确认。
创建基础备份
使用 pg_basebackup 把完整数据目录备份到 /root/work/base。
pg_basebackup -h 127.0.0.1 -U lab -D /root/work/base -Fp -Xs -P。-Fp 表示 plain 格式,-Xs 会同时流式传输备份期间生成的 WAL,使其可独立使用。后续恢复要复制该目录,不能修改原件。
记录要恢复到的时间
创建 ops_note 表并插入两行,再把当前时间保存到 /root/work/03-target.txt,作为恢复目标。
create table ops_note(id serial primary key, note text, at timestamptz default now())。使用 psql -tAc "select now()" > /root/work/03-target.txt 保存时间。插入后应等待至少 1 秒再记录,确保插入处于目标时间以内。
制造事故
删除全部 ops_note 行(delete from ops_note),但保留表。然后调用 pg_switch_wal(),把包含该变更的 WAL 推入 archive。
没有 WHERE 的 DELETE 是生产中常见事故。若不调用 pg_switch_wal(),最后一次变更可能尚未归档,恢复无法到达该位置,因为归档只复制已经写满的 segment。
创建恢复副本
把基础备份复制到 /root/work/restore,设置 restore_command、recovery_target_time、port = 5433,再创建 recovery.signal。
cp -r /root/work/base /root/work/restore && chmod 700 /root/work/restore。在 restore/postgresql.auto.conf 中写入:restore_command = 'cp /root/work/wal/%f %p'、recovery_target_time = '<03-target.txt 의 값>'、recovery_target_action = 'promote'、archive_mode = off、port = 5433。不要忘记关闭 archive_mode,否则恢复副本会覆盖原始 archive。
确认被删除的数据恢复
在 5433 启动恢复副本并检查 ops_note 行数,必须为两行。原始实例(5432)仍应为 0 行。
pg_ctl -D /root/work/restore -l /tmp/restore.log start -w -t 60。无法启动时查看 /tmp/restore.log,多数问题来自 restore_command 路径或目标时间格式。检查命令:psql -h 127.0.0.1 -p 5433 -U lab -d labdb -c 'select * from ops_note'。
理解逻辑备份为何不够
现在(事故之后)使用 pg_dump -Fc 把原始实例备份到 /root/work/labdb.dump。确认该 dump 中 ops_note 有多少行,并写入 07-dump.txt。
pg_dump -h 127.0.0.1 -U lab -Fc -d labdb -f /root/work/labdb.dump。列表命令为 pg_restore -l /root/work/labdb.dump。dump 是创建瞬间的快照,因此包含删除后的状态。如果只有昨晚的 dump,RPO 就是 24 小时。
总结缺少什么就无法恢复
在 /root/work/08-notes.md 中至少写三行:PITR 所需的三个组成部分、archive_command 把失败报告为成功时会发生什么,以及从 RPO 角度看只保留 dump 意味着什么。
正文必须包含 WAL、RPO、타임라인。最后一句概括了整门课程:从未恢复验证过的备份,不是备份。