跳至内容

32.2 恢复目标与时间线

恢复目标由三部分组成:

where to stop     time | xid | lsn | name | immediate | end-of-WAL
which history     timeline
what to do there  pause | promote | shutdown

少写任何一项,工具都会替你选默认值;默认值可能技术上合理,却不一定符合这次事故。 特别是 recovery_target_inclusive 默认是 onrecovery_target_timeline 默认是 latestrecovery_target_action 默认是 pause。事故 runbook 应显式记录有效值, 不能把“没有写”当成“没有语义”。

32.2.1 时间、LSN、事务 ID 与命名恢复点

PostgreSQL 每次只接受一个停止目标

PostgreSQL 18 的目标设置如下:

目标原生设置Pig 参数语义与适用场景
最早一致点recovery_target='immediate'-I在线 backup 完成的一致点
命名点recovery_target_name--name事前调用 pg_create_restore_point()
时间recovery_target_time-t/--time有可靠 commit 时间与时区证据
XIDrecovery_target_xid--xid已审计到目标事务提交身份
LSNrecovery_target_lsn--lsn已定位 WAL 边界
WAL 末尾不设置较早目标-d/--default尽可能恢复到 archive 末端

原生配置最多只能设置一个 immediate/name/time/xid/lsn 目标,否则启动报错 (Recovery Target Settings)。 end-of-WAL 是“重放所有可用 WAL”,不是“回到事故前”。

选择顺序可以写成:

reviewed named point exists?
  yes -> name
  no  -> exact bad commit XID independently audited?
           yes -> XID, then prove inclusive/exclusive
           no  -> exact WAL boundary independently derived?
                    yes -> LSN, then prove nearby transaction set
                    no  -> bounded UTC commit interval?
                             yes -> time, restore multiple candidates
                             no  -> continue evidence collection

time:人最容易理解,也最容易被时钟骗

时间目标接受 timestamp with time zone 语义。生产票据应写完整偏移:

2026-07-30 03:09:12.483+00
2026-07-30 11:09:12.483+08

不要只写:

03:09
yesterday 11:09
2026-07-30 11:09:12

Pig 可以为不带时区的输入补当前本地时区,但自动补全是 CLI 解析行为,不是事故证据。 执行计划应把目标归一化成完整时间戳,再由操作者对照日志、server timezone 和原始来源 复核。

时间目标配合 recovery_target_inclusive

inclusive=on   包含 commit timestamp 恰好等于目标的事务
inclusive=off  停在它之前

真实系统中多个事务可能共享很接近的提交时间;日志时间还有格式精度和采集延迟。时间目标 更适合表达候选区间,而不是把一条聊天消息的分钟级时间强行当成精确提交点。

XID:本章实验的目标,但不是天然全序号

XID 在事务开始时顺序分配,事务可以按不同顺序结束:

XID 500 begins
XID 501 begins
XID 501 commits
XID 500 commits

官方定义是:恢复那些在目标事务之前提交的事务,并根据 inclusive 决定是否包含目标 事务;不能用 xid < target 推断“更早提交” (recovery_target_xid)。

本章实验故意串行制造三笔事务:

safe-before -> damage -> post-target

所以 XID target 可无歧义地证明 inclusive/exclusive 差别。生产中若存在并发事务,仍要 从候选数据库查询业务 manifest,明确哪些并发提交被包含;XID 只是停止条件,不是业务 正确性的替代品。

不要依赖表行的 xmin 作为通用事故审计:

  • xmin 是行版本创建者,不直接表达删除者或完整事务影响;
  • UPDATE 会产生新行版本;
  • 一个事务可影响许多表,也可能回滚;
  • XID 会 wraparound,显示和比较需要理解 epoch;
  • freeze、复制与逻辑导出语义都可能让简单推断失效。

可控变更应在同一事务里记录 pg_current_xact_id() 与业务 change ID。

LSN:精确 WAL 位置,不自动等于业务提交

LSN 是 WAL 字节位置,适合:

  • 从日志或工具精确得到 commit record 附近边界;
  • 关联 archive segment 与 replay 进度;
  • 在两个候选之间做技术二分;
  • 证明 WAL 已覆盖到目标之后。

pg_current_wal_lsn() 是观察时的当前插入位置,不是自动返回“当前事务未来的 commit record LSN”。在事务内部随手记一个 LSN,再把它命名为 commit LSN,会造成伪精确。 如需按 LSN 恢复,应说明 LSN 如何获得、对应哪条 WAL 记录,并用候选数据验证。

LSN 同样受 inclusive 控制。它可以告诉 PostgreSQL在何处停,却不能告诉业务“账户余额 是否正确”。

name:最清晰,但必须提前创建

SELECT pg_create_restore_point(
    'before_price_rebuild_change_20260730'
);

命名恢复点适合重大 DDL、批处理和发布窗口。名称应包含 change ID,创建结果与 LSN 要进 变更证据,并确认对应 WAL 已归档。它不是 bookmark 服务:只有已执行并写入 WAL 的恢复 点才能在未来被找到,也不能用新 backup 恢复到该 backup 结束之前的 named point。

recovery_target_inclusive 只适用于 time、XID 和 LSN,不用于 name。若想表达“危险事务 之前”,应在危险动作前创建 restore point,而不是事后猜 named point 的包含语义。

immediate 与 end-of-WAL

immediate 在基础备份达到一致状态时结束,通常用于验证基础备份本身;它不包含 backup 之后的 WAL 业务变化。end-of-WAL 会尽可能重放全部可用 WAL,适用于主机损失后追到 最新,而不适合排除已经归档的误操作。

目标必须晚于所选基础备份的结束点。若想恢复到 backup 进行期间的更早时刻,需要选择 更早一份基础备份;不是把 target 参数写得更早就能穿越该下界。

32.2.2 timeline 分叉与“恢复到刚刚之前”

PITR 会产生新历史

原历史:

timeline 11
  A ---- B ---- damage ---- C ---- D

恢复到 damage 之前并 promote:

timeline 11
  A ---- B ---- damage ---- C ---- D
          \
timeline 12
            B' ---- E ---- F

新 timeline 防止恢复后生成的 WAL 覆盖旧历史。PostgreSQL 会产生 timeline history 文件,记录从哪条历史、哪个位置分叉;它们会像 WAL 一样归档,且是多分支恢复选择所必需 的证据 (Timelines)。

因此 timeline ID 不是“恢复次数”或“谁更新”,而是 WAL 历史分支身份。看到 timeline 变大也不能单独断言一次未授权 failover;要读 history、DCS、日志和变更记录。

currentlatest 的参照点

PostgreSQL 的定义非常具体:

current  沿基础备份创建时所在的 timeline 恢复
latest   沿 archive 中可找到的最新 timeline 恢复;默认值
N        沿明确的十进制 timeline
0xN      沿明确的十六进制 timeline

latest 对持续追随新 primary 的 standby 很有用,但在多次事故演练后,archive 里可能 已有后来创建的实验分支。若本次目标是原事故历史,盲用 latest 可能沿错分支。本章正式 实验显式使用 current,把恢复限定在 fresh backup 所属的 timeline 11;候选 promote 后各自在隔离目录进入新 timeline,但 archive_mode=off,不把实验分支推回 source repository。

复杂 re-recovery 必须画出 lineage:

backup label
backup start / stop LSN
backup timeline
target timeline
parent timeline and switchpoint
required history files
required WAL min/max
why latest/current/explicit is chosen

“仓库里有这个 WAL 文件名”仍不够。WAL 文件名前八位是 timeline 的十六进制表示;同一 逻辑 segment 位置在不同 timeline 上可属于不同历史。

“刚刚之前”是 exclusive 语义

time、XID、LSN 的 recovery_target_inclusive 默认是 on

on   stop just after target
off  stop just before target

如果目标 XID 正是误更新事务:

候选damage结论
inclusive / 默认存在应否决
exclusive / -X不存在才可能接受

这不是文案差别,而是一整笔事务是否存在。本章不只检查配置里写了 exclusive,还在 恢复候选中查询 damage audit、错误状态和 outbox 行,证明错误确实不存在。

target action 决定到点后的生命周期

action到点行为适用注意
pause暂停 WAL replay希望只读检查目标默认;需判断是否真的 paused
promote结束 recovery,开始接受连接隔离候选需完整验证或导出会创建新 timeline
shutdown到点后停库希望保留精确停点目录recovery.signal 仍在,重启前要处理配置

官方还规定:设置了目标,但 archive recovery 在到达它之前已经结束,server 会 fatal shutdown。不要把这种失败解释成“恢复到了最接近的位置”;目标没有到达就是阻断。

本章使用 promote,但只在 private Unix socket 上启动。promote 的“接受连接”不等于 接受业务流量,也不等于已重新接入 Patroni。

用两个候选替代一场争论

当团队争论目标是否应 inclusive 时,最稳妥的做法通常不是在事故群里投票,而是:

same backup
same target
same source timeline
same validation probes
candidate A inclusive
candidate B exclusive

然后让数据回答。候选差异应聚焦目标事务;如果两者还有其他意外差异,说明 backup、 timeline、并发提交或验证合同仍未被理解。

32.2.3 时钟、时区和证据误差

PostgreSQL 有不止一个“现在”

在一笔长事务中:

SELECT
    current_timestamp,
    statement_timestamp(),
    clock_timestamp();
  • current_timestamp / transaction_timestamp() 固定为事务开始时刻;
  • statement_timestamp() 是当前语句开始时刻;
  • clock_timestamp() 返回实际墙钟,会在语句执行中变化。

若审计列默认使用 current_timestamp,长事务提交时记录的值可能接近事务开始,不是 commit 时刻。日志行时间、应用 request time、消息 broker time 又分别来自不同进程。 “按 audit.created_at 恢复”之前必须先确认这个列的时间语义。

建立时钟证据

恢复票据至少记录:

PostgreSQL TimeZone and log_timezone
database host UTC time and synchronization state
application host UTC time
collector/queue timestamp semantics
client-provided timestamps and trust level
log timestamp precision
known NTP offset or uncertainty
conversion rule used by CLI

使用完整 UTC 或 numeric offset,不使用歧义缩写。官方建议 recovery time 使用数值偏移 或完整 zone name;缩写是否可用取决于更早加载的 timezone_abbreviations

把误差写进搜索区间

设:

  • $t_o$ 是观察到的时间;
  • $\epsilon_c$ 是时钟偏差;
  • $\epsilon_l$ 是日志/采集延迟;
  • $\epsilon_s$ 是时间戳精度与语义误差。

则候选区间至少为:

$$ [t_o-(\epsilon_c+\epsilon_l+\epsilon_s), t_o+(\epsilon_c+\epsilon_l+\epsilon_s)] $$

这不是统计置信区间,而是操作上的保守边界。区间越宽,越应恢复多个候选或改用独立 XID/name/LSN 证据。

夏令时与本地日期是事故放大器

2026-11-01 01:30:00 在采用夏令时回拨的地区可能出现两次;2026-07-30 作为 Pig time 输入会补为本地当天 00:00:00;只写 12:00:00 还会补“今天”。这些便捷格式 适合交互,不适合冻结事故目标。

生产票据应保存:

raw source timestamp
source timezone
normalized UTC timestamp
normalization tool/version
operator-reviewed target
inclusive/exclusive
candidate validation result

目标选择验收表

检查通过证据失败动作
一个目标effective config / Pig plan 只有一种 target修正冲突参数
target 可达backup end < target 且 archive 覆盖选更早 backup 或修复 WAL
timeline 正确history + backup lineage + explicit rationale不启动
inclusive 明确两候选或业务边界证明默认恢复两次
action 明确pause/promote/shutdown 与后续步骤一致修正生命周期
时区明确raw + UTC + offset + clock evidence扩大候选区间
业务探针明确damage/safe/good-after 预期回第 32.1 节

恢复计划至此才具有可执行语义。下一节把它放进一个不会覆盖现场、不会接入真实服务、 不会发送外部消息的隔离环境。


上一节:先界定误操作 · 返回本章目录 · 下一节:隔离恢复策略 · 查看全书目录 · 查看索引中心

最后更新于