故障切换与集群重建——力挽狂澜
33 故障切换与集群重建——力挽狂澜
第 20 章演练的是健康集群上的计划切换;第 31 章要求事故处理中先保护现场、分清 事实与假设;第 32 章处理“集群健康、数据却已经写错”的 PITR。本章面对另一条恢复 路线:
原主库已经不可用或不再可信,怎样在不制造两个可写主库的前提下接受新主库,并让 旧主库安全归队?
这不是一条 failover 命令的问题。操作者必须连续回答三个不同问题:
把三问混在一起,会产生两类相反事故:代理故障被误判成数据库故障,执行了一次多余 promotion;真正的主机分区又被当成普通进程退出,在旧主仍可能写时接受了新主。
学习完成标准
完成本章后,读者应能:
- 区分 PostgreSQL 进程、主机、存储、复制、DCS、代理与客户端失败;
- 从用户症状反向追踪服务路径,而不是把“连不上”直接翻译成“主库宕机”;
- 用 Patroni、SQL、操作系统、DCS 与客户端证据共同判定当前数据库角色;
- 解释 WAL 的 sent、write、flush、replay 位置分别代表什么;
- 解释 timeline history、共同祖先与历史分叉,而不是把 timeline 当版本号;
- 理解“数据看起来最新”只是候选条件之一,不等于可安全提升;
- 列出 Patroni 候选的可达性、tag、lag、timeline、同步状态与 watchdog 条件;
- 区分 DCS 多数派、PostgreSQL 副本数量和业务写多数派,避免混用“quorum”;
- 解释
failsafe_mode为什么要求 incumbent primary 联系全部已知成员; - 为旧主选择进程、watchdog、云/虚拟化、电源、存储或网络 fence,并写出证据;
- 区分 automatic failover、planned switchover 与 manual failover;
- 在自动化不能证明 authority 时停下来转人工,而不是强制选一个候选;
- 判断
pg_rewind的 lineage、停机、checksums/wal_log_hints、WAL 与权限前提; - 在 rewind 失败或前提不明时,转向可信 backup 或 fresh base backup;
- 重建后复核复制槽、连接端点、客户端未知结果、监控和备份;
- 分开报告检测时间、围栏时间、控制面恢复、客户端写缺口与数据损失;
- 用 Pigsty/Patroni 命令执行动作,再回到 PostgreSQL 原生证据验收;
- 输出一份包含失败域、authority、timeline、token 对账、复位与结论边界的证据包。
三个平面,四道门
一次 HA 事故至少横跨三个平面:
| 平面 | 关键事实 | 典型证据 |
|---|---|---|
| 数据面 | 谁可写、WAL 到哪里、哪条 timeline | pg_is_in_recovery()、LSN、sender/receiver、control data |
| 控制面 | 谁持有 leader lock、谁可竞选、自动化是否暂停 | Patroni REST/CLI、DCS revision、动态配置、tags |
| 服务面 | 客户实际连到哪里、池与代理何时摘挂节点 | HAProxy/PgBouncer health、DNS/VIP、端到端 token |
恢复路径依次通过四道门:
任何一道门为 unknown,都不能用“业务很急”把 unknown 改写成 true。可以在事故指挥下
明确承担风险,但风险接受必须被记录,不能藏在 --force 后面。
本章的核心不变量
故障切换不是“副本变成主库”,而是维持下面的不变量:
这里的 accepted 很重要。网络分区两侧可能各有一个 pg_is_in_recovery()=false 的
PostgreSQL;只看本机 SQL 会得到两个“主库”。要让其中一个可被系统接受,还必须有
authority 与 fence:
路由摘除只能防止正常客户端访问旧主,不等于围栏。定时任务、直连、复制、维护脚本或 网络另一侧仍可能写入旧主。真正的 fence 必须使它失去写能力,或至少使它无法继续产生 会被业务接受的状态,并能由独立证据验证。
正式实验
本章在已确认的四节点 Pigsty 开发沙箱完成两段真实实验。
第一段对 managed pg-test:
实际候选是 pg-test-3。这不是脚本预期之外的杂音,而是实验最重要的结果之一:
自动竞选必须接受“任一满足条件的副本”,runbook 不能把某个候选预写成已经发生的事实。
正式观测:
服务路径通过 Unix socket 回源,inet_server_addr() 为 NULL,因此 runner 没有伪造
后端地址,而是用“客户端观察到的 timeline + 同期 Patroni 拓扑”归属提交:旧 timeline
确认 18 次,新 timeline 确认 112 次。
第二段在 pg-test-3 的一次性目录创建同源临时集群 A/B/C:
两个分叉 primary 从不同时运行。结果:
这些是几十 MB 的本机虚拟化沙箱观测,不是生产 RTO。环境使用异步复制、单 etcd、
watchdog off;没有注入断电、存储故障、真实网络分区,也没有执行破坏性的 managed
reinit。最终门禁保持 production_ch33_gate=pending。
阅读前后关系
本章目录
33.1 先识别失败域
33.2 复制状态与时间线证据
33.3 自动故障转移的保护条件
33.4 DCS 故障的安全处理
33.5 旧主重加入与集群重建
33.6 切换与重建 runbook
33.7 实战:主库故障与 DCS 干扰
权威参考
PostgreSQL:
Patroni:
Pigsty:
上一章:PITR 与误操作恢复——妙手回春 · 返回下卷导读 · 下一章:过载保护与资源故障判型——李代桃僵 · 查看全书目录 · 查看索引中心
33.1 先识别失败域
“连接失败”是用户看到的症状,不是失败域。它可能来自 DNS、证书、连接池、HAProxy、 主库进程、整台主机、磁盘阻塞或 DCS;这些故障需要完全不同的动作。没有定位失败域就 执行 promotion,相当于用一次有数据风险的拓扑变更修一个尚未证明属于数据库的问题。
先画当前请求真正经过的路径:
每一条箭头都可以失败。事故调查应从失败请求出发逐跳验证,同时从 PostgreSQL 和 Patroni 反向确认角色,最终在同一 UTC 时间线上汇合。
33.1.1 进程失败、主机失败与存储失败
三类失败不能只用 ping
| 失败域 | 可能观察 | 尚不能推出 | 首要动作 |
|---|---|---|---|
| PostgreSQL 进程 | SSH/OS 正常,端口或 socket 失败 | 主机或磁盘必坏 | 看 Patroni、postmaster、日志与 I/O |
| Patroni 进程 | PostgreSQL 可能仍运行,REST 失败 | PostgreSQL 已被围栏 | 查 service、leader lease 与 watchdog |
| 主机 | SSH、REST、数据库同时不可达 | 主机已断电 | 从虚拟化/BMC/云控制面取证并 fence |
| 存储 | I/O error、fsync 延迟、只读文件系统 | 其他节点也坏 | 停止扩大损伤,验证数据目录与备份 |
进程退出是最容易围住的故障。若主机仍可控,可以明确停止 Patroni/PostgreSQL,并从 systemd、PID、REST 和 SQL 四处取得“不能写”证据。本章实验就是这种窄模型:
这证明的是 process fence,不是 hardware fence。
主机不可达更危险。ping 超时可能是 ICMP 被过滤,SSH 超时可能只是管理网故障,云 API
显示 running 也不说明内核还能调度 PostgreSQL。若无法从故障主机自身证明停机,应使用
独立控制面:
- BMC/IPMI 或电源控制器断电;
- 云或虚拟化平台 stop/fence,并等待实例状态与网络状态收敛;
- 存储租约或卷 detach,保证旧主无法继续访问可写数据;
- 硬件/软件 watchdog 在 Patroni 失去心跳时使节点重启;
- 经审计的网络 fence,阻断所有写入路径而不只是一条客户端路由。
“我连不上它”不是 fence evidence。一个从管理网不可达、从业务网仍可服务的旧主,正是 最典型的脑裂来源。
存储故障不一定表现为进程退出
存储卡顿时 postmaster、REST 和端口都可能仍在,健康检查却因超时把节点摘除。要同时 检查:
若进程因 I/O hang 无法及时 demote,单纯 systemctl stop 也可能卡住。此时需要 watchdog
或外部 power/storage fence。不要为了让命令“成功返回”直接 kill -9 Patroni;杀掉
控制进程并不自动杀掉仍可写的 postmaster,反而可能移除最后一层角色管理。
先写可证伪假设
如果反证出现,就回到失败域判断,不要硬把它解释成预期剧本。
33.1.2 网络分区、客户端不可达与代理误判
“数据库可用”有多个观察位置
至少分开下面四个探针:
local SQL 成功、service SQL 失败,优先调查服务路径;service SQL 成功、某个应用失败, 优先调查 DNS、证书、凭据、连接池和应用网络。所有 SQL 都失败而 Patroni REST 正常, 再看 PostgreSQL 状态与健康检查条件。
代理健康检查回答的是一个谓词,不是宇宙真相。Patroni 的 /primary 或 /read-write
只有在节点为 primary 且持有 leader lock 时返回成功,适合写服务;replica/read-only
服务需要不同 endpoint。若 HAProxy 误用了普通 /patroni,replica 也可能被当成写后端。
审阅时写出:
网络分区没有单一“网络坏了”
用方向矩阵描述:
| 来源 → 目标 | DCS | primary REST | replica REST | SQL | client |
|---|---|---|---|---|---|
| old primary | ? | local | ? | local | ? |
| candidate | ? | ? | local | local | ? |
| observer | ? | ? | ? | ? | ? |
primary -> DCS 失败、replica -> DCS 成功,与所有节点都失去 DCS 是两种场景;
primary -> replica 失败、replica 仍可见 DCS,又与客户端单向不可达不同。只写
“network partition”无法决定谁应 demote。
网络恢复时还可能出现陈旧连接:
- 应用池里已有到旧主的 session;
- DNS 缓存仍解析旧 VIP;
- PgBouncer server connection 尚未重建;
- 长事务在切换前已开始,客户端只看到断线;
- 客户端超时,但 COMMIT 实际已在新主落盘。
因此切换后必须按幂等身份对账 unknown outcome,不能把所有网络错误都当作“未执行” 再裸重试。
代理摘除不是旧主围栏
把旧主从 HAProxy backend 移除只能约束走这条代理的客户端。以下 writer 可能绕过它:
服务面隔离是必要条件,却不能替代数据面的 stop/watchdog/power/storage fence。
33.1.3 主库失败、复制停滞与控制面失败
先问“谁坏了”,再问“要不要切”
| 当前事实 | 默认路线 |
|---|---|
| primary 可写,replica replay 停滞 | 修复制、保 WAL;通常不切主 |
| primary 不可写且已围栏,候选健康 | 自动或受控 failover |
| primary 健康,代理服务失败 | 修服务路径,不 promotion |
| DCS 不可达,数据库角色仍稳定 | 保护角色、查 failsafe/网络,不重置选举 |
| 多个节点自称 primary | 事故升级;先停止外部写和取得 fence |
| system identifier 不同 | 不是同一复制集;禁止提升 |
副本坏了不需要主库故障切换。PostgreSQL 官方文档也区分 primary failure 与 standby failure:standby 可重启就继续 recovery,不可恢复再创建新 standby。为了让“拓扑看起来 整齐”而切主,会给业务增加一次没有必要的中断。
控制面状态与数据库事实要成对记录
一个最小事实包:
不要只贴 patronictl list 截图。DCS 故障时 CLI 自己可能无法读取状态;SQL 能显示本机
角色,却不知道它是否仍持有 authority;代理能显示后端健康,却不知道数据 lineage。
相互矛盾正是事故信号,不能挑一个最方便的来源当真相。
五条立即停手线
出现任一项,自动动作转人工:
- 旧主是否仍可写为 unknown;
- 观察到两个 writable PostgreSQL;
- 候选 system identifier 或 timeline history 不明;
- DCS 权威分区与数据库可达分区不一致;
- 无法说明客户端 unknown transaction 如何对账。
转人工不是“直接运行 manual failover”。它意味着冻结进一步自动化、保留当前事实,由 事故指挥人明确风险、候选、fence、回退和业务 owner,再授权下一动作。
本节检查表
进入候选选择前,应能完成:
下一节将把其中的 replication 与 timeline evidence 展开。
返回本章目录 · 下一节:复制状态与时间线证据 · 查看全书目录 · 查看索引中心
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,不能悄悄忽略。
上一节:先识别失败域 · 返回本章目录 · 下一节:自动故障转移的保护条件 · 查看全书目录 · 查看索引中心
33.3 自动故障转移的保护条件
自动故障转移的价值,是在预先证明过的条件内缩短决策时间;它不是在信息不足时替人 猜测。一个安全状态机应当宁可暂时没有可写主库,也不接受两个相互冲突的写 authority。
简化后的 Patroni 路径:
ttl、loop_wait 与 retry_timeout 会影响探测和选举节奏,但端到端 RTO 还包括
PostgreSQL 停止/恢复、DCS 延迟、候选 replay、代理健康检查、连接重建和客户端重试。
33.3.1 候选健康、数据风险与多数判断
候选先通过资格门
候选至少需要:
| 条件 | 目的 | 证据 |
|---|---|---|
| Patroni REST 可达 | 能参与管理与健康检查 | exact endpoint/status |
| 同 system identifier | 属于同一物理 lineage | pg_control_system() |
| WAL/timeline 可接受 | 不跳回旧分支 | LSN、history、check_timeline |
| lag 在策略内 | 控制异步数据风险 | pg_stat_replication 与 policy |
nofailover=false |
未被运维策略排除 | Patroni tags |
| replay 未异常暂停 | promotion 后能完成 recovery | recovery/receiver state |
| required watchdog 可用 | 满足本节点 leader 前提 | Patroni/watchdog status |
| 同步模式条件满足 | 遵守 sync/quorum policy | DCS sync state |
“候选服务可连”没有覆盖这些条件。一个被刻意延迟 30 分钟的 reporting replica 可能非常 健康,却绝不能自动成为业务主库。
三种“多数”不要混为一谈
三节点 PostgreSQL 加单节点 etcd,并不会因为“数据库还有 2/3”就拥有 DCS 多数; 三节点 etcd 加两节点 PostgreSQL,也不会自动使 PostgreSQL 写入成为多数派复制。DCS 负责协调 leader authority,WAL durability 由 PostgreSQL replication mode 决定。
生产设计需要分别画失败域:
异步可用性换取数据风险
默认异步模式允许 primary 在副本未确认时提交。故障后提升某个满足最低健康条件的
standby,未复制到它的事务会留在旧分支。maximum_lag_on_failover 限制候选在最近
观测时的 WAL gap,却不覆盖观测后到故障间的新 WAL。
所以报告应写:
而不是只写 RPO < 1MB。WAL 字节不是业务订单数,unknown 也不等于丢失。
同步模式能收紧正常单故障的数据风险,但也有可用性和复合故障边界。切换报告仍应验证 业务 identity,不把配置名当作结果。
自动选择不等于随机选择
Patroni 按当前资格与状态参与 leader race;操作者不应从成员列表顺序推断赢家。本章
正式 run 的两个 replica 都合格,实际 pg-test-3 提升。正确验收是:
如果业务要求指定节点,使用 planned switchover 或明确 tags/拓扑策略,不能把自动选举 伪装成固定候选。
33.3.2 fencing 旧主与防止双写
fence 的目标是消除旧 authority
旧主围栏的验收谓词:
或者在特殊设计中:
第二种更难证明,因为必须覆盖所有客户端、job、CDC、消息和外部副作用。数据库 HA 通常优先选择第一种:让 PostgreSQL 停止、只读、失去存储或节点断电。
围栏层次
| fence | 能证明 | 主要盲点 |
|---|---|---|
| PostgreSQL clean stop | postmaster 不再写 | I/O hang 时可能无法完成 |
| Patroni demote/stop | 角色管理与数据库按协议停止 | 控制进程自身可能失效 |
| watchdog reset | Patroni 未续喂时节点重启 | 设备/权限/超时配置必须真实可用 |
| BMC/cloud power off | 主机失去计算能力 | 控制面状态延迟、自动重启策略 |
| storage detach/revoke | 旧主失去可写数据 | 本地缓存、detach 完成语义 |
| network isolation | 阻断已枚举的路径 | 漏掉网络、job 或控制路径 |
| proxy/backend removal | 正常服务不再路由 | 不是数据库 fence |
Patroni watchdog 是一层额外保护:若 mode 为 required 且不能启用 watchdog,节点拒绝
成为 leader;leader 正常运行时必须持续喂狗,否则 watchdog 在超时后触发 reset。它
不能只存在配置文件里,演练要验证设备、权限、超时和真实 reset。
Pigsty 的 patroni_watchdog_mode 支持 off、automatic、required。本章沙箱为
off,所以正式 run 明确只声称 process fence,不声称硬件 watchdog。
顺序不变量
要求 $t_1 \le t_2$。如果无法观测真实 promotion 瞬间,至少在“接受候选为可服务” 之前完成 fence evidence;不要用事后日志倒推一个未经保护的时间窗不存在。
本章正式结果:
这是一台可控虚拟机上的 graceful systemd stop。主机断电、内核 hang 与存储 stall 需要不同 fence,不能复用这组时延。
双主之后不要急着“选数据多的”
若已经观察到两个 writable primary:
- 阻断外部写,记录每条路由与 writer;
- 保存两边 system identifier、timeline、LSN 与业务 identity;
- 取得至少一边的可信 fence;
- 由业务 authority 选择权威分支;
- 隔离提取另一分支独有的合法事实;
- 用逻辑对账合并,而不是直接让它重新加入;
- 从权威分支 rewind 被舍弃的一侧,或对其做全量重建。
pg_rewind 会舍弃 target 的分叉变化。未先抢救业务事实就 rewind,等于主动销毁可能
需要审计的数据。
33.3.3 自动化不确定时何时转人工
转人工的含义
转人工不是自动执行:
它意味着把状态机停在安全边界,要求人补齐:
三种切换入口
| 入口 | 适用 | leader/candidate | 风险 |
|---|---|---|---|
| automatic failover | 已验证故障与预设策略内 | 由 Patroni/DCS 竞选 | 策略边界内自动 |
| planned switchover | 健康 leader 的维护切换 | 可显式指定 | 可预检、通常低风险 |
| manual failover | leader 不可用且自动路径不能完成 | 必须明确 candidate | 可能放宽 lag/sync 条件并丢数据 |
Patroni REST 文档明确警告 manual failover 可能导致数据损失;无 leader 时,手工候选 可能不受自动 failover 的全部 lag/sync 检查。它是一项风险接受,不是“更强的修复命令”。
Pig 的入口:
--plan 只展示工具计划,不能替代 fence 与 SQL 证据。执行参数以现场 --help 为准;
structured execution 通常还要求显式确认。
自动化停止线
| 不确定性 | 自动动作 |
|---|---|
| old primary writable? unknown | 禁止接受新主 |
| candidate lineage unknown | 禁止 promote |
| DCS split direction unknown | 禁止删 key/重建 DCS |
| manual candidate lag unknown | 禁止 force failover |
| client unknown outcomes unbounded | 可以恢复服务,但不能宣称 RPO |
| backup/archive health unknown | 可以先恢复 HA,必须保持生产 gate pending |
自动化可以并行采集和收敛证据,但不能把超时本身当作安全事实。一个 60 秒 timeout 只能 证明“在观察位置没看到预期状态”,不能证明旧主已经断电。
人工决策记录
没有这份记录的 --force,在复盘时无法区分有意识的风险接受与操作失误。
上一节:复制状态与时间线证据 · 返回本章目录 · 下一节:DCS 故障的安全处理 · 查看全书目录 · 查看索引中心
33.4 DCS 故障的安全处理
Patroni 依赖 DCS 保存 leader lock、动态配置与成员协调状态。某节点无法更新 leader lock, 可能是 DCS 整体故障,也可能是该节点落在网络分区的错误一侧。从单个节点看,两者很难 区分:
安全默认是按最坏分区处理:旧主不能续约 authority,就要在 lease 失效前停止写,避免
另一侧取得 lock 后出现双主。failsafe_mode 是一个有严格条件的例外,不是忽略 DCS。
33.4.1 DCS 不可达、失去多数与延迟
先区分四种现象
| 现象 | 可能原因 | 不能立即做 |
|---|---|---|
| 一个 Patroni 到 DCS 超时 | 节点网络、DNS、凭据、DCS | 宣称 DCS 整体宕机 |
| 所有 Patroni 到 DCS 失败 | DCS 失去 quorum、公共网络 | 直接 bootstrap 新 DCS |
| DCS 请求慢但可成功 | 负载、磁盘、网络、GC | 只调大 TTL 掩盖 |
| DCS 有 quorum,某分区不可见 | 不对称网络 | 在不可见一侧手工提升 |
调查矩阵:
同时记录 UTC、monotonic clock、请求耗时和 DCS revision。只记录一次
endpoint health=true 会漏掉尾延迟和间歇超时;只看平均值又会漏掉超过
retry_timeout/lease deadline 的长尾。
DCS 多数派属于 DCS
etcd 通常部署奇数成员。三成员容忍一成员故障,五成员容忍两成员故障;单成员没有 冗余。PostgreSQL 数据节点数量不会补足 etcd quorum。
Pigsty 生产设计应让 infra/DCS 与 PostgreSQL failure domain 相互审阅:
- DCS 成员跨独立电源、主机或可用区;
- latency 满足 lease 与运维目标;
- client/peer 网络和证书可用;
- 备份 DCS 配置与凭据恢复流程,但不把陈旧快照直接覆盖活集群;
- 监控 leader changes、fsync、peer RTT、容量与 auth failure;
- 演练成员故障和 quorum 丢失,不只测
systemctl status。
本章沙箱只有一个 etcd,不能演示多数派。正式实验因此不停止 DCS,只用 blind decision scenario 验证 runbook。
延迟故障比“down”更隐蔽
DCS 还能响应,但延迟接近 Patroni deadline 时可能出现:
正确动作是保存 latency distribution、Patroni loop 时间和 lease revision,处理控制面
性能根因。盲目增大 ttl 会延长故障检测与服务恢复;盲目减小又会让正常长尾触发抖动。
参数变更必须作为容量与失败注入实验,而不是事故现场的猜测。
33.4.2 先保护当前数据库角色,不盲目重置选举状态
failsafe_mode 的严格语义
Patroni DCS failsafe mode 启用后,incumbent primary 在 DCS leader lock 更新因特定
连接类错误失败时,可以向 /failsafe 中全部已知成员发送 Patroni REST 请求。
只有全部成员确认它仍是当前 primary,它才可以继续作为 primary。
为什么不是多数副本确认?DCS quorum 的网络分区与 PostgreSQL 成员分布可能不同。如果 primary 只联系到某个“数据库多数”,DCS 可写分区里的少数 PostgreSQL 节点仍可能取得 leader lock。要求 ALL known members,才能让任何可能竞选的成员知道 incumbent 仍活着。
因此:
若已知成员之一不可达,incumbent 应 demote;DCS 恢复前不会凭空得到新的 authority。
不要删除你还没理解的 key
危险动作:
leader key 是协调事实,不是“卡住的锁文件”。删除它会触发新的 leader race,却不会 自动停止旧主;如果旧主仍写,删除 key 正好制造双主窗口。
先保护:
- 当前 SQL 角色和客户端写入口;
- leader lock、revision、member 与 failsafe 内容;
- 每个 Patroni 的 REST 角色与可达性;
- DCS member/quorum 与 auth 状态;
- 旧主 fence 能力;
- WAL/archive/backup 现场。
需要禁止写时,围住精确业务入口或数据库 authority,不要用破坏 DCS 历史的方式达到 “看起来没主库”。
六个决策场景
本章 failure-model.json 固定六种:
| 场景 | 默认决策 |
|---|---|
| 所有 Patroni 失去 DCS、彼此 REST 全可达 | 观察 failsafe incumbent,不发起新竞选 |
| 仅 primary 失去 DCS、可达全部成员 | 验证 failsafe handshake,replica 不竞选 |
| primary 失去 DCS 且看不到一名已知成员 | old primary 必须 demote/fence 后才接受候选 |
| 仅 replica 失去 DCS | 排除该 replica,保持当前 primary |
| DCS latency 接近 timeout | 控制面事故,停止人工 promotion,保存时间证据 |
| 代理故障伪装成数据库故障 | 修服务路径,不切数据库 |
正式 run 随机抽到“primary 同时失去 DCS 和一个 replica”。正确答案不是立即 promote, 而是先证明旧主只读/停止或由外部 fence 隔离,再确认幸存侧的 DCS authority 与候选 WAL。本次只做决策演练,没有真实注入分区。
33.4.3 恢复控制面后核对 leader lock 与数据库事实
恢复顺序
不要在第 1 步完成后直接开放流量。DCS 恢复只说明控制面重新可读写,数据库可能已经有 两个分支或有成员保留陈旧角色。
四层互证表
| 层 | 唯一主库应看到 | 异常例子 |
|---|---|---|
| DCS | one valid leader lock | no lock / stale identity |
| Patroni REST | one primary with lock, replicas elsewhere | two primaries / unknown |
| SQL | accepted node recovery=false,followers=true | DCS leader SQL 仍 recovery |
| service | write health only points accepted primary | old backend remains healthy |
如果 DCS 指向 A、SQL 却显示 A 在 recovery、B 可写,不要为了让表格一致而手改 key。 先停止流量,保存两边日志、control data 和 timeline,查明谁何时改变角色。
推荐证据命令
SQL:
在 primary 上另查 pg_stat_replication;在 replica 上查 pg_stat_wal_receiver。所有
输出带采集位置、UTC、monotonic sequence 与 hash。REST/DCS 可能含敏感认证信息,证据
包只保留必要 projection,不导出密码、token 或完整配置。
DCS 恢复完成标准
production_gate=pending 时可以继续修复与验证,但不能把技术恢复自动升级成业务批准。
上一节:自动故障转移的保护条件 · 返回本章目录 · 下一节:旧主重加入与集群重建 · 查看全书目录 · 查看索引中心
33.5 旧主重加入与集群重建
新主稳定后,旧主不能直接“启动看看”。它可能仍停在共同祖先,也可能已经在旧 timeline 产生分叉写。归队的目标不是让进程起来,而是构造一个只读 follower:
若旧主在 promotion 前已经干净停止、没有产生分叉,它可能直接沿 timeline history 继续 recovery;若已经分叉,则要 rewind 或全量重建。
33.5.1 pg_rewind 的前提、失败与验证
pg_rewind 做什么
pg_rewind 将 target PGDATA 同步到 source 所在的权威分支。它根据 timeline history
找到共同祖先,从 target 读取分叉之后发生变化的数据块,并从 source 复制所需页面;
新文件、配置文件和 WAL 等按文件复制。它通常比全量 base backup 少复制很多数据。
典型关系:
它不会把 A1/A2 与 B1/B2 做业务 merge。target 分叉事实若需要保留,必须在 rewind
前从隔离实例提取。
前提清单
| 前提 | 原因 | 验证 |
|---|---|---|
| 同 system identifier | 必须来自同一 cluster ancestry | pg_controldata / control function |
| timeline 有共同祖先 | 才能确定分叉点 | history files |
| target 已停止 | 文件不能继续变化 | service/PID/lock evidence |
target 启用 checksums 或 wal_log_hints |
识别修改页面所需 | init/control/config |
full_page_writes=on |
rewind 安全前提 | source/target config evidence |
| source 一致且可信 | source 是要保留的权威历史 | authority decision |
| 所需 WAL 可取得 | target 启动后要从共同 checkpoint replay | source/archive coverage |
| target 可写、空间充足 | rewind 会修改整个目录 | filesystem preflight |
| recovery config 正确 | 启动后必须跟随 source | -R 与配置复核 |
PostgreSQL 18 默认 initdb 启用 data checksums,但不能把“默认”当现场事实。老集群、升级 集群或定制 initdb 可能不同。
source 与 target 方向不能写反
target 会被改写,source 被读取。生产命令应从 inventory/incident record 生成,并在执行 前打印 secret-free plan:
不要把带密码的连接串写入工单或证据;使用受控 service/password file 或短期凭据。
clean shutdown 与失败语义
pg_rewind 要求 target cleanly shut down。默认情况下,若 target 非干净停止,工具会
尝试单用户模式完成 crash recovery;--no-ensure-shutdown 可让它直接报错。生产流程
更适合先显式处理 crash recovery、保留日志与控制信息,再决定是否允许工具自动动作。
更重要的是:PostgreSQL 官方文档警告,rewind 中途失败后 target 很可能不再处于可恢复 状态,推荐取得 fresh backup。不要:
保留失败日志、目录 manifest 与 source 身份,然后把 target 视为待重建。
配置与 WAL 复核
rewind 会从 source 复制配置文件,target 的本机差异可能被覆盖:
port、socket、listen address;- SSL key/certificate symlink;
- tablespace path;
primary_conninfo与 slot;- archive/restore command;
- include 文件和本机路径。
使用 -R/--write-recovery-conf 会创建 standby.signal 并写 recovery connection,但仍要
检查目标端本机覆盖。缺失从共同 checkpoint 到 source 当前状态的 WAL 时,target 启动
仍会失败;应确保 source pg_wal、archive 或 --restore-target-wal 路径满足需求。
验收不是“pg_rewind done”
还要验证:
本章一次性实验的 A 在 rewind 后只看到 base、new-primary、after-divergence,
看不到 old-primary-divergent,并以 streaming standby 启动。
33.5.2 从备份或新基础备份重建
什么时候跳过 rewind
任一项成立时优先全量重建:
- system identifier 或共同祖先不可信;
- checksums/
wal_log_hints前提不满足; - 所需 WAL 缺失且无法恢复;
- target 存储疑似损坏;
- rewind 中途失败;
- target 文件权限、tablespace 或 symlink 状态复杂且不可验证;
- 数据规模不大,全量路径更简单、更可预测;
- 合规要求使用已验证 backup lineage。
优化目标不是复制字节最少,而是总风险最低:
$$ Cost = copy_time
- uncertainty
- validation
- rollback_risk
- operator_complexity $$
fresh base backup
原生流程示意:
必须确认 target 是精确授权的空目录。不要对变量为空、符号链接或宽泛 glob 执行删除; managed member 的数据目录应由 Patroni/Pigsty reinit 流程管理。
Pig/Patroni 路径:
当前 Pig 帮助明确警告:reinit 会删除目标成员数据并从 leader 重建。它是破坏性动作, 必须核对:
本章没有执行 managed reinit,因为“编写书籍”并不等于授权删除托管副本。实验用 exact
临时目录 C 真实运行 pg_basebackup -R,证明机制后立即停止并删除。
从已有 backup 重建
大集群从对象存储/pgBackRest backup restore,可能比从 primary 传全量 base backup 更 少占生产网络,并提供明确 lineage。选择时比较:
| 来源 | 优点 | 代价 |
|---|---|---|
| current primary basebackup | 最新、路径直接 | 消耗 primary I/O/网络,长 copy 要保 WAL |
| replica basebackup | 减少 primary 压力 | source 必须合格且允许 |
| pgBackRest backup + archive | 可复用已验证备份 | 需要 restore + WAL catch-up |
| volume snapshot | 快 | 一致性、加密、跨主机与 lineage 要证明 |
无论来源,最终都必须 streaming 到 accepted primary,并验证业务 marker,而不只看目录 复制完成。
估算恢复窗口
粗略下界:
$$ T_{\text{rebuild}} \ge \frac{bytes\ to\ transfer}{effective\ throughput}
- WAL\ catchup
- validation $$
若 copy 期间 primary 持续产生 WAL:
还要计算 replication slot 导致的磁盘增长,避免“为了重建副本”把 primary pg_wal
撑满。
33.5.3 复制槽、端点和客户端状态清理
复制槽不是自动清理垃圾
成员失联期间,physical slot 可能继续保留 WAL。重建前后检查:
不要仅因 slot active=false 就删除。它可能正为待恢复成员、logical subscriber 或
备份流程保留 WAL。先映射:
Patroni 管理 permanent/member slots 时,还要核对 DCS 成员与 slot policy,避免手工 删除后被重建或导致 WAL gap。
服务端点要清掉陈旧状态
重建成功后:
- Patroni REST role 为 replica;
- HAProxy write health 不接受它;
- read service 是否允许加入由 lag/业务策略决定;
- PgBouncer server connection 已重建;
- VIP/DNS owner 与 TTL 正确;
- direct-IP 配置与运维脚本没有指向旧角色;
- application
target_session_attrs=read-write等约束生效。
“节点回到集群”与“可以承载读流量”不是同一门。刚重建副本可能仍在 catch-up、缓存 全冷、统计未热、备份未覆盖。
客户端 unknown outcome
故障窗口内:
对账:
每个 unknown 应归类为 absent 或 committed once。没有 idempotency identity 时,无法用 数据库技术准确判断“同一业务动作是否可重试”,必须升级业务 owner。
本章实验:
30 个 unknown 最终均 absent;如果其中有 committed once,也仍可正确归类。验证器关心 “全部可对账”,不要求网络故障时 unknown 必须为零。
重建完成清单
上一节:DCS 故障的安全处理 · 返回本章目录 · 下一节:切换与重建 runbook · 查看全书目录 · 查看索引中心
33.6 切换与重建 runbook
runbook 不是命令收藏。它把每个动作绑定到触发条件、证据、风险、停止线、成功谓词与 复位路径。切换场景尤其要防止“命令执行成功”替代“唯一 authority 已建立”。
建议每一步都使用:
33.6.1 计划切换、故障切换与人工干预入口
先选路径
第 20 章已经完整演练 healthy planned switchover;本章正式 run 是 controlled process fault 后的 automatic failover,最后才用 planned switchover 复原教学基线。
R0:只读预检
加上 SQL:
检查:
R0 也要创建 evidence timestamp,不能把几分钟前的健康状态当动作瞬间事实。
R1/R2:计划切换
版本选项以 pig pt switchover --help 为准。执行前:
- current leader 与 candidate 都来自 fresh structured state;
- candidate replay 在窗口内;
- 长事务、DDL、备份和批任务已评估;
- client probe 已开始;
- backout candidate 可用;
- 路由与 connection drain owner 在线。
执行后不能立刻退出:
R2/R3:故障与手工切换
自动 failover 通常不需要操作者再发一条 failover 命令;重点是确认 fence、候选和服务 收敛。manual path:
计划应明确警告 leader 不可用时可能丢失未复制事务。执行前需要独立 reviewer:
不要用 manual failover 修复 proxy、replica 或 DCS 延迟问题。
重建入口
reinit 会删除目标 replica 的 PGDATA,属于 R3。它只应在:
- target 已确认是 replica;
- target 数据无需再取证;
- accepted primary/source 明确;
- rewind 不适用或已失败;
- 带宽、WAL retention、tablespace 和密钥已准备;
- post-validation 与 failure cleanup 已写好;
- 两人复核具体 member。
本章 runner 永不执行 managed reinit。
33.6.2 Patroni、DCS、代理和 SQL 证据互证
证据矩阵
| 问题 | Patroni | DCS | SQL | 服务/客户端 |
|---|---|---|---|---|
| 谁是 primary | REST role | leader lock | recovery=false | write health |
| 旧主是否排除 | member state | lease/failsafe | unavailable/recovery | no accepted route |
| 候选是否最新 | timeline/lag | member/sync state | LSN/receiver | token boundary |
| 切换是否完成 | one leader | lock updated | followers streaming | writes succeed |
| 数据是否丢失 | 不直接回答 | 不直接回答 | token/ledger query | client ack manifest |
任一行中出现冲突,都应保存而不是“修正输出”。例如:
这不是运行一个 edit-config 的理由,而是 authority contradiction。先停止写、查 timeline 与动作日志。
采集顺序避免自我污染
如果先 restart/reinit 再取证,就会丢失原 PID、日志、control data 和 timeline 现场。 如果先删除 fixture 再对账,就无法证明 unknown 是否提交。
Secret-free projection
完整 Patroni、DCS 与 inventory 配置含密码、token、证书路径。证据只投影:
本章 journal evidence 只保留行数、整体 SHA-256 和 pattern counts,明确
raw_log_exported=false。需要审计原日志时,在受控现场保存,不把它发布到教材。
monotonic 与 UTC 双时钟
UTC 用于跨主机关联,monotonic 用于本机阶段时长:
NTP 校时可能让 wall clock 跳变,不能用两个 UTC 字符串相减替代 monotonic duration。 跨主机 monotonic 不能直接比较,因此 fence/promotion 关键顺序最好由同一 observer 记录,另用多主机 UTC/clock offset 辅助。
证据 bundle
公开摘要
failover-run.json 不含 inventory、密码、原始日志或
临时 service 文件。
33.6.3 集群恢复后重新建立监控与备份健康
HA 恢复不是事故关闭
新主可写后,至少检查:
切换会改变产生 WAL、执行 cron/job、归档、备份和 logical publisher 的节点。只验证
SELECT 1 会漏掉一半恢复工作。
复制与 slot
验收:
- 所有预期 replica streaming;
- 无未知 sender;
- slot owner 与 member/subscriber 对应;
pg_walretained bytes 有界;- rebuild member replay 到验证 LSN;
- logical replication origin/subscription 状态正确。
archive 与 backup
更稳妥的事故关闭门是:在新 topology 上完成一次新的可恢复点,并在计划窗口验证 side restore;至少不能让 backup/archiving warning 被“主库已恢复”盖掉。
服务与应用
- HAProxy 只接受当前 write endpoint;
- PgBouncer 不保留错误 server connection;
- stale DNS/VIP 已收敛;
- application pool 重建;
- old timeline transaction 全部断开或明确结束;
- unknown idempotency token 对账完成;
- scheduler 单实例语义恢复;
- outbox/CDC offset 没有重复或空洞;
- cache/search 等派生系统按权威数据库重建。
监控重新设基线
切换会让:
这些不是都应静默。给事故窗口加 annotation,保留告警作为证据,再针对已解释的瞬态 调整状态;不要批量关闭告警后忘记恢复。
关闭条件
技术项全绿但 owner_acceptance=pending 时,事故仍不能被描述成“业务已完全恢复”。
上一节:旧主重加入与集群重建 · 返回本章目录 · 下一节:实战:主库故障与 DCS 干扰 · 查看全书目录 · 查看索引中心
33.7 实战:主库故障与 DCS 干扰
本节把前六节变成三条可重复、但风险边界不同的证据链:
小节标题中的“随机注入主机、网络或 DCS 症状”指从 blind scenario library 随机抽取 症状;正式 online mutation 只有可自动复位的进程 fence。单 etcd、watchdog off 的共享 沙箱不具备安全、真实地证明不对称网络分区和 DCS quorum 的条件。
33.7.1 随机注入主机、网络或 DCS 症状
先读合同
- 环境、动作和验收:
requirements.json - 五类失败域与六个 DCS scenario:
failure-model.json - 人类可读安全边界:
lab-contract.md - managed 与 disposable 拓扑:
topology.mmd - 33 个反例:
negative-cases.json
静态检查不连接远端:
它检查合同、failure model、负例集合与 15 个 hash-bound source files。只读现场快照:
capture 投影:
它不读取 inventory,不修改数据库、DCS、服务或路由。
完整演练 guard
inventory 必须为 mode 0600。private_client_service.py 从中只提取 fixture 用户密码,
生成一次性 mode-0600 libpq service file;密码不会进入 evidence,退出时 exact private
directory 被删除。
任一 guard 不符,runner 在 mutation 前以 77 退出。evidence directory 已非空也拒绝, 避免把两次 run 混成一份报告。
online fault 为什么选 service stop
runner 运行:
这是明确、可复位的 L2 动作。它不是:
systemctl stop 返回后还不算 fence。runner 单独检查:
只有 fence 成立,后续候选才可被接受。
随机 DCS scenario
同一 run 用系统随机源从六个 scenario 选一个。本次抽到:
blind observation:
incumbent primary 失去 DCS,同时不能联系 failsafe set 中一名 replica。
正确决策:
evidence 保存 decision_only=true、
live_dcs_fault_injected=false、live_network_partition_injected=false 和
leader_key_deleted=false。教材不把桌面推理冒充在线故障结果。
33.7.2 保护旧主、选择候选、测量 RTO/RPO
fixture 与客户端
runner 在 test.pg36_ch33 创建 exact marker schema:
客户端每 200 ms:
网络/连接异常记录 outcome=unknown,不把错误字符串或密码写入 evidence。探针在 fault
前必须已有 acknowledgement,且在 topology stable 后还要有新 timeline acknowledgement,
否则不能测量切换窗口。
候选在运行时产生
preflight:
合同只声明 eligible set:
没有指定 expected winner。正式结果:
开发 runner 的早期版本曾硬编码 pg-test-2,真实运行立即暴露了错误。本章把这次失败
转成负例 evidence.failed.leader=...:validator 必须接受任一 eligible winner,并拒绝
把未观察候选写成事实。
时序
正式公开证据
failover-run.json:
| 阶段 | 观测 |
|---|---|
systemctl stop patroni |
1.582 s |
| action start → process fence | 1.817 s |
| action start → Patroni topology stable | 4.536 s |
| old primary start → streaming | 2.527 s |
| planned switchover back | 2.832 s |
| maximum client acknowledgement gap | 6.212 s |
为什么 client gap 大于 control-plane stable:
因此 4.536 秒是 observer 看到 Patroni stable 的控制面时间;6.212 秒是 synthetic client 连续两个成功 acknowledgement 的端到端缺口。二者都不是完整业务 RTO,后者仍未包含 事故发现、人工确认和所有应用恢复。
timeline 归属而不是伪造 IP
服务从 HAProxy/PgBouncer 通过 Unix socket 回源,PostgreSQL 的
inet_server_addr() 返回 NULL。runner 没有把 NULL 强行替换成配置 IP,而是:
结果:
这套归属只在切换窗口 timeline 唯一前进、baseline restore 在 probe 结束后才执行的合同 内成立。若窗口内多次切换,应再加入 server identity 或 commit audit。
unknown outcome 对账
对每个 unknown,runner 按 (run_id, attempt_no, token) 查询新主,得到 committed once
或 absent。本次 30 个都 absent。若某个 committed once,它也不是失败;只有无法分类
才是 unresolved。
怎样写 RPO
错误写法:
本次正确写法:
在 160 次 synthetic idempotent INSERT、异步
pg-test和该服务路径条件下,130 个 客户端已确认 token 全部在新历史中存在一次;30 个 unknown 全部对账,已知 fixture 数据损失为 0。没有证明任意生产事务或复合故障下的零 RPO。
若应用没有 idempotency key/ledger,客户端 acknowledgement 与数据库 WAL 位置之间就 缺少可对账身份,RPO 只能保持 unknown。
旧主归队与基线恢复
runner 启动 pg-test-1 后要求:
然后才执行 planned switchover:
若任一阶段失败,recovery handler 先启动由本 run 停止的服务,再读取实际唯一 leader;
只在三成员健康后从该 runtime leader 切回 pg-test-1。它不假设 winner 必为某节点。
33.7.3 重建旧主并验证时间线、端点和业务写入
为什么另做 disposable lab
managed 旧主在 controlled stop 后没有分叉,Patroni 可以直接让它跟随新 timeline;这
不能证明 pg_rewind。真实 managed reinit 又会删除副本 PGDATA,超出本章授权。
所以 runner 在 pg-test-3 创建:
全部 listen_addresses=''、Unix socket mode 0700,不加入 Patroni/DCS/代理/备份。
分叉状态机
每次开始另一分叉写入前先停止当前 primary,因此
concurrent_divergent_primaries=false。这避免为了演示 rewind 真制造并发双主。
rewind 验收
pg_rewind 快,是因为临时数据极小且本地缓存/磁盘路径很短。它不代表 TB 级集群
rewind 时间;生产还受修改页面比例、WAL/archive、tablespace、同步与存储影响。
full rebuild 验收
两个时延不可拿来比较“rewind 一定比 basebackup 慢/快”:样本极小,basebackup 初始源、 checkpoint 与缓存条件不同。实验要证明的是两条机制都能构造正确 follower,以及失败时 有明确 fallback。
33 个反例
validate.py 对真实 evidence 逐个变异:
正式结果:
这不证明代码没有 bug,但能防止“只检查工具 exit 0”“清理失败仍通过”“沙箱结果冒充生产” 等结构性错误。
复核现有 bundle
all 只重建 validation/public summary 并扫描 evidence,不停止服务、不连接 secret
inventory、不触发 failover/rebuild。
最终边界
正式证据能够支持:
- controlled process fence 先于候选接受;
- Patroni 从 eligible set 自动选择唯一新主;
- endpoint 上的 synthetic token 可完整对账;
- 旧主以 streaming replica 归队;
- planned switchover 恢复教学基线;
- 同源分叉可用
pg_rewind收敛; - rewind 不适用时 fresh base backup 能构造 follower;
- exact fixture/root 被清理。
不能支持:
- 主机断电、内核 hang、存储损坏的等价性;
- 真实不对称网络分区下没有脑裂;
- 单 etcd 代表生产 DCS quorum;
- watchdog fencing 已验证;
- 异步复制的生产零 RPO;
- managed
reinit的工时与存储集成; - 6.212 秒是生产 RTO SLO。
因此公开证据的最终决策始终是:
上一节:切换与重建 runbook · 返回本章目录 · 下一章:过载保护与资源故障判型——李代桃僵 · 查看全书目录 · 查看索引中心