20.1 从失败模型设计高可用
高可用设计最常见的错误,是先问:
正确的第一问是:
节点数是答案的一部分,failure model 才是题目。
20.1.1 进程、主机、磁盘、网络、机房与控制面故障
故障、失效与事故
先统一三个词:
一个 fault 未必立刻成为 service failure。反过来,组件都显示 running,
客户端也可能因 DNS、pool、证书或连接风暴而无法提交。
所以失败模型的 observation point 必须从“进程是否活着”扩展到:
不同 failure domain
| 层次 | 例子 | 可能影响 | 不能靠什么证明已覆盖 |
|---|---|---|---|
| PostgreSQL 进程 | crash、OOM、assert | 单实例停止 | 三个 PID 都在 |
| OS/主机 | kernel panic、reboot、供电 | 主机上全部组件 | 三个 VM 名称 |
| 存储 | device loss、stall、fs corruption | 数据/WAL 不可读或卡死 | RAID 标签 |
| 网络 | 丢包、分区、非对称路由 | DCS、复制、客户端视图分裂 | ping 一次 |
| zone/rack | switch/PDU/机架 | 一组主机共同失效 | 不同 IP |
| site/region | 机房、运营商、灾害 | 整个本地 HA 集群 | 同城三副本 |
| control plane | etcd/API/DNS/PKI | 不能安全选主或发现服务 | PostgreSQL 可查询 |
| client path | HAProxy/PgBouncer/driver | 数据库正常但业务不可用 | Patroni 显示 leader |
| human/change | 错 DDL、错配置、误删 | 正确地复制错误 | replica healthy |
每个 failure domain 至少写四件事:
“三节点”不是故障域证明
本章沙箱有三台 pg-test VM,但它们:
因此:
如果宿主机休眠,三台 VM、DCS、proxy 与本地备份目标可能一起消失。形式上 的 replica count 没有覆盖共同依赖。
生产设计应显式记录 placement:
“不在同一台机器”只是最低一层。
网络分区比断网更难
完全断网容易理解;危险的是不同观察者看到不同世界:
此时目标不是让两边都“尽量可用”,而是避免两个可写历史同时接受不可合并 的提交。选主只决定谁获得 authority;还必须让失去 authority 的旧主停止 提交,或从客户端路径隔离。
存储故障不只有“磁盘没了”
真实存储 fault 包括:
一个极慢但未死的 primary 可能比 crash 更难处理:
- health check 仍偶尔成功;
- leader loop 得不到调度;
- shutdown 超过租约;
- client 请求堆积;
- WAL sender/replay 指标变得陈旧。
因此 failure injection 不能只有 kill -9 postgres。但本章也不会冒险把这些
场景一次性塞进共享 laptop 沙箱;它们被列入
failure-model.json,明确标记为
not-injected。
控制面与数据面
至少区分:
DCS 不可用时,已有 PostgreSQL 连接可能暂时工作;这不意味着能安全进行 新一轮选主。数据库进程可用和自动 HA control 可用不是同一个布尔值。
共同模式故障
一个严肃的 failure model 会问:
“每台主机都健康”无法发现下一次发布会同时把三台配置写坏。
场景卡
每个场景用同一模板:
没有 assumptions 和 stop conditions 的“演练步骤”,更像破坏脚本。
本章实际覆盖什么
九个场景中,正式观察的只有:
client-session-loss 得到一次采样观察,但完整 routing contract 留给第 22
章。其余故障不能从本次结果反推。
20.1.2 RPO、RTO、降级目标与数据风险
RPO 是允许退回多远
令:
则时间口径的实际恢复点损失可写为:
但 PostgreSQL 复制更常先观测 WAL byte:
[ gap_{bytes}
LSN_{primary}-LSN_{candidate} ]
两者不能直接互换。相同 1 MiB WAL:
- 高峰可能只代表几十毫秒;
- 低峰可能跨越很久;
- 一条关键订单与一批可重建日志的业务损失不同。
所以 RPO 合同至少要说明:
“RPO < 1 MB”与“任何已确认订单都不会丢”不是同一句话。
RTO 从哪个时刻算到哪个时刻
一个故障路径可拆成:
不同仪表测到不同终点:
| 时钟 | 开始 | 结束 | 能回答什么 |
|---|---|---|---|
| component | 进程 fault | PostgreSQL running | 进程恢复 |
| control | lease/health failure | 新 leader 稳定 | 控制面迁移 |
| service | endpoint first fails | 新连接可写 | 接入恢复 |
| transaction | 业务动作发起 | 可判断结果 | 用户恢复 |
| backlog | 故障开始 | 积压清空 | 完整业务恢复 |
patronictl 返回时间不是 application RTO。HAProxy 健康检查通过也不等于
已有 pool/session 已恢复。
计划切换没有故障检测阶段
本章正式动作由操作者在健康状态发起:
所以观测的 action-to-stable ≈ 5.823 s 不能代替主机故障 RTO。后者还要
包括 detection、lease expiry、candidate decision、可能的 fencing 与
client retry。
objective、measurement 与 promise
建议用三个字段避免混淆:
一次 observation 不能建立 percentile。通过一次演练只说明:
degradation target
availability 不是只有 up/down。故障期间可定义:
| 能力 | 目标状态 | 禁止状态 |
|---|---|---|
| 已有读事务 | 可失败并重连 | 静默读到错误 authority |
| 新写事务 | 短暂拒绝 | 两个 primary 同时接受写 |
| 只读查询 | 可从合格 replica 提供 | 把陈旧数据冒充强一致 |
| 后台任务 | 暂停/排队 | 无幂等地重复 |
| 管理写 | 人工冻结 | 绕过服务直连旧主 |
一致性优先的系统宁愿短时不可写,也不接受两个历史。降级目标必须由业务 owner 接受,而不是数据库团队在事故中临时猜。
提交风险有三种客户端状态
对一次业务写:
网络错误或 session 被 HAProxy 关闭,只能告诉客户端“连接失败”,不能自动 证明事务回滚。盲目重试:
可能造成重复业务动作。
本章 probe 为每次尝试生成:
结果未知后不把它改成一个新 token 重做,而是在服务稳定后查 token:
正式运行 25 个 unknown token 全部核对为 absent。这个结果描述本次选择的 最终历史;应用仍需决定 absent 后是否以及如何重试。
数据风险不止“丢几行”
还包括:
因此 HA acceptance 必须同时观测服务可用性和数据语义。
目标卡
一个可评审的目标:
是否能实现,要由同步策略、failure placement、服务路径和客户端能力共同 回答。
20.1.3 高可用不等于备份,也不等于零数据丢失
三种能力回答不同问题
| 能力 | 主要问题 | 典型机制 | 无法独自处理 |
|---|---|---|---|
| HA | 当前 primary 不能服务怎么办 | replica、election、routing | 逻辑误删、历史恢复 |
| backup/recovery | 要回到过去或异地怎么办 | base backup、WAL archive、PITR | 秒级接管 |
| data protection | 哪些确认写必须存在几份 | sync policy、placement、storage | 客户端重连 |
replica 是当前历史的追随者;backup 是可选择的历史恢复材料。
replica 会忠实复制错误
以下操作通常会进入 WAL 并传播:
物理副本不会判断这是事故。它的健康意味着复制系统工作,而不是数据仍符合 业务真相。
可以用 delayed replica 降低某些操作错误风险,但它仍不是完整 backup 策略:
- 延迟窗口有限;
- 主机/账户/自动化可能共故障;
- 到点仍会重放错误;
- 恢复流程与一致性仍需验证;
- 不能替代离线/跨域历史和 retention。
第 21 章会把 restore proof 作为独立 gate。
异步复制不承诺零 RPO
PostgreSQL 流复制默认异步。primary 可以先向客户端确认,再把最新 WAL 传给 standby。若 primary 的未传输尾部永久丢失:
PostgreSQL 18 官方文档 明确说明异步 log shipping 存在这类窗口;streaming 可以缩小窗口,不会 用“通常很小”把它变成零。
本章实际配置:
所以即使一次健康 switchover 所有 acknowledged token 都存在,也只能说明:
不能说明:
同步复制也不是魔法
同步提交提升指定失败范围内的 durability,但 guarantee 依赖:
如果 primary 与当前同步 standby 同时失效,或操作者强制提升一个落后
candidate,仍可能丢失 acknowledged history。Patroni 文档也把
synchronous_mode 的数据保护与 write availability 代价放在一起解释,
而不是承诺任何故障组合都零损失。
backup 也不是“文件存在”
备份成立至少需要:
本章看到 archive_mode=on 与归档统计,不会因此宣布 PITR 已通过。
archive 命令是否持续成功、repository 是否独立、恢复链是否完整,全部留给
第 21 章实测。
能力矩阵
| 场景 | HA replica | backup/PITR | application design |
|---|---|---|---|
| primary 进程 crash | 主要 | 兜底 | reconnect |
| primary host 永久损失 | 主要 | 兜底 | unknown outcome |
| 整个机房损失 | 取决于跨域 | 主要 | regional failover |
| 误删表 | 会复制错误 | 主要 | guard/repair |
| duplicate retry | 无法识别业务重复 | 不能预防 | idempotency |
| 数据静默错误 | 可能传播 | 历史/校验 | invariant |
| ransomware/credential compromise | 可能同受影响 | immutable/offline | identity/response |
三条生产阻断线
以下任一成立,就不能把 HA gate 判为生产通过:
同样:
一个 gate 通过不能替别的 gate 签字。
本节检查
请为自己的服务写出:
- 六类 failure domain 与共同依赖;
- 一个 eligible scenario 的 RPO/RTO 起止点;
- 允许的降级和绝不允许的状态;
- acknowledged / rejected / unknown 三种提交结果的处理;
- HA、backup 与 application 各自 owner;
- 三条 stop condition。
如果答案仍是“我们有三台,所以高可用”,这一节还没有完成。
小结
权威参考
- PostgreSQL 18:Log-Shipping Standby Servers
- Patroni:Replication modes
- Patroni:DCS failsafe mode
- 本章 failure model
- 本章实验合同
返回本章目录 · 下一节:物理流复制 · 查看全书目录 · 查看索引中心