33.2 复制状态与时间线证据
故障切换前,复制证据回答两个不同问题:
只比较一个“延迟 0 MB”不能回答第二问;只看 timeline 相同,也不能说明最后一笔已确认 事务已经到达候选。
33.2.1 发送、接收、重放位置与延迟
一条 WAL 有多个位置
primary 上的 pg_stat_replication 包含:
standby 上的 pg_stat_wal_receiver 则从接收端描述 upstream、written/flushed 与最近结束
位置。对一个候选 $r$,可以定义:
字节差是 WAL 距离,不是秒数。相同 1 MB 在空闲系统可能代表很久,在写入高峰可能只 代表毫秒;累积统计也有采样与刷新周期。
primary 侧只读检查:
standby 侧:
“flush 到了”与“业务可见”分开
promotion 关心 durability,读服务关心 replay visibility。一个 standby 可能已经 flush 某段 WAL,却因 replay pause、冲突、I/O 或恢复延迟尚未应用;promotion 后它会 继续完成 recovery,但切换时长和读一致性仍受影响。
同步复制也必须说明同步到哪一层:
synchronous_commit |
primary 等待的主要确认 |
|---|---|
on |
synchronous standby flush |
remote_write |
standby OS write |
remote_apply |
standby replay |
local / off |
不以 remote durability 作为提交门槛 |
不要仅凭 sync_state='sync' 宣称零损失,还要核对当前事务的 commit 策略、同步成员、
同步节点故障组合与 Patroni 模式。
异步 RPO 不是一个固定配置值
Patroni 的 maximum_lag_on_failover 是候选过滤条件,不是精确 RPO。官方 replication
mode 文档指出,primary WAL 位置不是实时连续采样;异步最坏损失还包括最近采样之后
继续产生的 WAL。可以把风险粗略写为:
$$ RPO_{\text{bytes}} \lesssim maximum_lag_on_failover
- WAL_{\text{after last observation}} $$
业务数据损失又不等于 WAL 字节数。最终要用业务 identity:
本章实验有 30 次客户端 unknown,但对账后全部能分类,已确认 token 缺失为 0。这只 说明该 fixture,不把异步策略改写成生产零 RPO。
33.2.2 timeline、历史文件与分叉
timeline 是历史分支
standby promotion 会创建新的 timeline。新 timeline 的 history 文件记录它从父 timeline 的哪个 WAL 位置分叉:
timeline 数字较大只说明产生过后续分支,不自动说明该节点包含最多业务提交。必须结合 history 的父子关系和 fork LSN。两个节点还必须具有相同 PostgreSQL system identifier; system identifier 不同意味着根本不是同一物理集群 lineage。
SQL 控制信息:
注意 pg_control_checkpoint().timeline_id 是控制文件中最近 checkpoint 的 timeline
事实,不是任意时刻的分布式 authority。将它与 Patroni REST/DCS、当前 WAL 文件名和
recovery 状态一起解释。
分叉发生在“旧主继续写”时
设共同祖先为 $F$:
A 与 B 都可能是内部一致的 PostgreSQL 历史,但不能把两边物理 WAL 直接拼接成一条
历史。旧主归队要选择一个权威分支:
- 保留新主 B;
- 丢弃旧主 A 在分叉后的变化;
- 用
pg_rewind把旧主变成 B 的 standby; - 若 A 中有需要抢救的业务事实,先隔离提取并由业务 owner 对账,不能让 A 重新服务。
PostgreSQL 官方 pg_rewind 正是用 target timeline history 找共同祖先,并复制分叉后
目标上需要替换的页面和文件。它不是双向 merge。
recovery_target_timeline=latest
HA standby 通常应跟随新 promotion 产生的最新 timeline。PostgreSQL warm standby 文档
建议多 standby 的 HA 场景使用 latest(也是默认),否则 standby 可能到父 timeline
终点后停住,不能沿新主继续。
但 PITR 与 HA 的“latest”语义不同:
- HA follower 通常要跟随当前权威新主的最新分支;
- 事故恢复可能故意选择 backup 所在的
currenttimeline,避免误入后来分支; - 明确 timeline 必须能由 history 与恢复目标证明。
这也是第 32 章为何把 target timeline 写入恢复合同。
本章正式 timeline 证据
managed 演练:
disposable 实验则故意制造:
只有 timeline 前进而没有 marker 验证,不能证明选对分支;只有 marker 正确而没有 system identifier、receiver status 和 cleanup,也不是完整归队证据。
33.2.3 数据最新不等于可以安全提升
候选资格是条件交集
一个可接受候选更接近:
Patroni 的健康候选检查包括 REST 可达、nofailover 未启用、required watchdog 可用、
lag 不超过 maximum_lag_on_failover;启用 check_timeline 时还检查 timeline。同步
模式还会考虑同步成员;manual failover 在无 leader 时可能放宽部分 lag/sync 条件,
因此它比自动 failover 更需要人工风险确认。
读取状态时至少看:
不同版本的输出选项以本机 pig pt ... --help 为准。不要让脚本解析面向人的表格列宽,
也不要从显示顺序推断“第一个 replica 就会提升”。
为什么正式实验没有指定候选
演练前 pg-test-2 与 pg-test-3 都为 streaming、同 timeline、lag 在策略内。最初若
把 pg-test-2 写成“expected candidate”,实际 Patroni 却选择 pg-test-3,验证器
会错误判定一个健康自动切换失败。
正确合同是:
生产若必须固定候选,应把需求变成显式操作和策略:planned switchover 指定 candidate, 或通过 tags/拓扑策略定义资格;不能一边声称自动竞选,一边在文档里假装结果已确定。
“最新”仍可能不安全的五种情况
- 节点 system identifier 不同,只是碰巧有相似业务表;
- 节点在较旧 timeline,缺失已经被接受的新主历史;
- 节点被
nofailover标记,可能承担延迟副本或特殊任务; - 旧主尚未 fence,即使候选有最新 WAL,提升仍可能双写;
- DCS authority 不明,候选看到的“无 leader”只是分区后的局部视图。
此外,最新 standby 可能位于与旧主相同的失败域:同机架、同电源、同 SAN、同 hypervisor。 数据最新不等于失败独立性。
提升前证据卡
缺一项不一定永远不能提升,但必须把缺失变成显式 risk acceptance,不能悄悄忽略。
上一节:先识别失败域 · 返回本章目录 · 下一节:自动故障转移的保护条件 · 查看全书目录 · 查看索引中心