跳至内容

20.4 选主、DCS 与防脑裂

PostgreSQL 原生提供 replication、promotion 与 recovery primitives,但不替 多台实例决定:

谁现在被允许写?
谁最适合接管?
旧主何时必须停止?
客户端如何只找到被授权的主?

Patroni、DCS、watchdog 和 service routing 分别补上这条链的不同环节。 把它们都叫“自动选主”,会丢掉最重要的安全边界。

20.4.1 Patroni、租约、leader lock 与健康判断

leader 是有期限的 authority

Patroni 使用 DCS 中的 leader key/lock 表达:

某 member 在一个租约窗口内拥有 primary authority

它不是永久铭牌。当前 leader 必须周期性更新;失去更新能力时,旧 authority 必须在新的 candidate 可能获得 authority 之前失效。

关键周期:

ttl
  leader lock 的租约尺度

loop_wait
  HA loop 大致运行间隔

retry_timeout
  DCS 操作重试边界

本章实际 dynamic policy:

ttl=30
loop_wait=5
retry_timeout=10
maximum_lag_on_failover=1048576
pause=false
failsafe_mode=true

这些值共同影响 detection 与 decision latency。不能只拿 ttl=30 直接写出 “RTO=30s”,还需 health path、fence、promotion、service check 与 client recovery。

health 有多个观察层

Patroni candidate eligibility 可能考虑:

member API alive
PostgreSQL state
replication state and lag
timeline
tags: nofailover/nosync/...
scheduled maintenance
sync safety state

服务代理又可能使用 Patroni REST endpoint:

/primary
/replica
/read-only

客户端 SQL 则看到:

SELECT pg_is_in_recovery();

三层应当一致,但 authority 不同:

证据回答
DCS/Patroni谁拥有 HA authority
PostgreSQL SQL当前实例是否 recovery、复制事实
service health/routing新连接会被送到谁
client transaction业务是否恢复且结果可判断

只看一层无法完成 HA acceptance。

patronictl list 是快照

sudo -iu postgres \
  patronictl -c /etc/patroni/patroni.yml \
  list pg-test

它适合:

member/host
role/state
timeline
lag snapshot
scheduled actions

但输出不是审计历史,也不证明旧主已物理隔离。正式实验把结构化 JSON 与 SQL phase 一起保存,而不是只贴一张终端截图。

candidate “最新”也需要定义

异步集群可能没有一个 candidate 包含 primary 的最后 WAL tail。Patroni maximum_lag_on_failover 控制候选落后上限的一部分,但官方文档指出位置 并非实时连续采样,实际 worst case 还包括最近一个周期生成的 WAL。

所以:

maximum_lag_on_failover=1 MiB

不是:

RPO always <= exactly 1 MiB

更不是 zero RPO。

timeline eligibility

同 system identifier 的旧分支 member 也可能不适合提升。check_timeline 等政策可限制 candidate timeline;本章没有把未观察的配置写成已启用。

正式验收另行要求:

all current members report same Patroni timeline per phase
primary WAL-derived timeline agrees
timeline advances on both switchovers
system identifier never changes

这是针对本次 planned transition 的 lineage proof。

pause 与 maintenance

Patroni paused mode 会改变自动管理行为。任何切换前都要显式查看:

patronictl show-config pg-test

本章 preflight 要求 pause=false。如果 cluster paused:

  • 不应假设自动 failover 会工作;
  • 不应直接照抄 runbook;
  • 先确认是谁、为何 pause,以及安全恢复路径。

DCS 是协调 authority,不是数据真相

DCS 保存 leader lock、dynamic config 和 member metadata;业务行仍在 PostgreSQL,WAL lineage 由 PostgreSQL 证明。

不应:

只因 DCS 里写着 leader 就忽略 SQL recovery state
只因 SQL 可写就绕过 DCS authority
把 DCS backup 当 PostgreSQL backup

HA 要求 coordination truth 与 data-plane truth 对齐。

20.4.2 fencing、watchdog 与旧主隔离

脑裂是什么

不是监控上短暂出现两个 running,而是:

two diverging histories can accept writes

尤其危险的状态:

old primary retains client reachability
old primary lost DCS authority
new primary acquired authority
different clients write both sides

事后不能靠 WAL replication 自动 merge 两边业务。

fencing 的目标

在新 primary 接受写之前,确保旧 primary:

stopped
demoted read-only
power/storage/network fenced
or otherwise unreachable from all write clients

fence 可以在不同层实现:

手段局限
processPatroni stop/demote PostgreSQLPatroni 若失调度可能失败
hostwatchdog reset、STONITH需真实硬件/权限/验证
storagerevoke writer attachment/lease依赖 storage semantics
network/serviceproxy 不路由旧主直连可能绕过
applicationauthority token/epoch应用复杂度高,仍需底层安全

多层互补,不能拿 HAProxy health check 替代 host fencing。

为什么 stop 也可能来不及

Patroni watchdog 文档 列出:

Patroni process crashed/OOM
PostgreSQL shutdown too slow
host load high
VM paused
HA loop not scheduled

此时普通“租约更新失败后 stop PostgreSQL”逻辑可能没有机会及时执行。

watchdog

Linux watchdog 接收 heartbeat;超过窗口未喂狗,系统被 reset。Patroni 在 成为 leader 前可以激活 watchdog,并在 demotion 后禁用。

重要模式:

off
  不提供 watchdog fence

automatic
  可用时使用

required
  不能激活就拒绝成为 leader

准确名称和行为以所用 Patroni 版本为准。

本章实际:

watchdog.mode=off
device=/dev/watchdog
safety_margin=5

所以正式结论必须保留:

EX20-WATCHDOG-OFF
hardware-watchdog fencing unqualified

不能因为 planned switchover 中旧主正常 demote/rejoin,就宣称 VM pause 或 Patroni crash 时也安全。

service routing 是最后一道,但不是唯一一道

Pigsty primary service 通常用 Patroni /primary health check,只把新连接 送给当前 primary。即使旧 PostgreSQL 进程还可接受直连,只要 Patroni API 不再报告 primary,HAProxy 可以停止把 primary service 流量送过去。

这很有价值,但有边界:

direct 5432/6432 connections can bypass service
stale existing sessions may differ from new routing
another network path may still reach old primary
health endpoint itself depends on Patroni process

生产必须治理直连权限与网络路径,而不是只发布“推荐使用 5433”。

fence proof

一个未计划故障演练至少应证明:

old primary loses authority before/when new authority starts
old primary cannot accept write through any authorized path
client service selects only new primary
old primary later rejoins chosen timeline or is rebuilt
logs/DCS timeline explain ordering

本章没有注入这类 fault,因此不声称完成。

手工 promote 是高风险动作

当 DCS/网络视图不清楚时:

pg_ctl promote
patronictl failover --force

不是“恢复服务的快捷键”,而是选择一条新可写历史。操作前必须知道:

old primary state
candidate WAL position/timeline
who has authority
which clients are drained
what data loss is accepted
how old primary will be fenced

不满足就 stop,而不是用 --force 消除不确定性。

20.4.3 DCS 可用性与数据库可用性不是同一件事

两种 availability

database availability
  当前 authorized primary 能否继续完成事务

HA control availability
  系统能否安全更新 lock、选新 primary、改变动态配置

DCS outage 时,已有 primary 可能短时仍有数据服务;同时系统无法安全建立新 authority。这不是矛盾,而是两个 control boundary。

为什么失去 DCS 时通常选择保守

单个 member 无法仅凭“我连不上 DCS”区分:

DCS 全部 down
自己被网络隔离
另一侧仍可达 DCS 并会选新 leader

如果它乐观继续写,另一侧又选主,就可能脑裂。因此传统安全选择是:

cannot renew authority -> demote before lease expiry

Patroni DCS failsafe mode

failsafe_mode 试图在特定 DCS failure 中保留已有 primary:

current leader cannot update DCS
but can reach all known Patroni members via REST
all members acknowledge it
then it may continue as primary

若任何已知 member 不响应,则 demote。Patroni DCS failsafe 文档 强调检查 all members,而不是随意取 Patroni member 多数,因为 DCS 与 PostgreSQL placement quorum 可能不是同一个视图。

本章观察 failsafe_mode=true,但没有让 etcd 失效或切网络,因此:

configured intent observed
behavior under DCS loss not tested

单节点 etcd 不是 HA DCS

正式沙箱只有一个 etcd member:

dcs_endpoint_count=1

它足以验证:

Patroni integration
leader lock path
planned switchover
dynamic config capture

不能验证:

DCS quorum survives one member loss
cross-AZ DCS placement
etcd election latency
split network behavior

这就是 EX19-SINGLE-ETCD 在第 20 章继续生效的原因。

DCS 也有自己的运维合同

生产需要:

odd-sized quorum where appropriate
independent placement
latency budget
capacity/compaction
TLS and identity
backup/restore
version/upgrade
monitoring
access control

不要让一个为数据库提供 HA 的组件,自己成为没人负责的单点。

control-plane outage runbook

建议先判断:

1. current PostgreSQL client service still可用吗?
2. leader lock最后一次成功更新是什么时候?
3. DCS 是全局 down 还是局部 partition?
4. current leader 能否看到所有 Patroni members?
5. watchdog/fence 是否真实有效?
6. 是否允许保持 current leader,还是必须冻结写?
7. 谁有权执行 manual action?

禁止:

simultaneously restart every Patroni/DCS member
force promote while old primary unknown
delete DCS keys to “reset state”
initialize a new DCS namespace without lineage proof

数据面正常时也要保留事故证据

若 client 暂时无感,不代表无需 incident:

HA redundancy may be gone
next fault may become outage
dynamic config changes may be unavailable
leader authority safety margin is reduced

应记录:

first/last DCS error
member reachability matrix
leader loop logs
lease/TTL
failsafe requests
client SLI
manual actions

本章的停止线

只要出现:

two possible leaders
unknown old-primary write reachability
system identifier mismatch
ambiguous timeline
DCS authority absent and failsafe condition unproven

就不继续 planned switchover,也不自动尝试“恢复”。先冻结写路径、保留证据、 升级 decision authority。

本节证据

当前三台 member 都要报告:

scope=pg-test
member_name exact
patroni_version=4.1.3
dcs_kind=etcd3
dcs_endpoint_count=1
watchdog.mode=off

dynamic config 必须来自 DCS:

ttl=30
loop_wait=5
retry_timeout=10
maximum_lag_on_failover=1048576
synchronous_mode=false
failsafe_mode=true
pause=false
use_pg_rewind=true
use_slots=true

公开采集器只导出 allowlist,不把完整 Patroni YAML 中的 credential 复制进 evidence。

小结

leader is leased authority
health is layered evidence
election without fencing can still split brain
watchdog protects when user-space demotion may not run
service routing is necessary but bypassable
DCS availability != database availability
configured failsafe != tested failsafe
single etcd blocks production HA inference

权威参考


上一节:同步策略与提交语义 · 返回本章目录 · 下一节:切换、故障转移与重加入 · 查看全书目录 · 查看索引中心

最后更新于