# 自动故障转移的保护条件

LLMS 索引： [llms.txt](/llms.txt)

---

自动故障转移的价值，是在预先证明过的条件内缩短决策时间；它不是在信息不足时替人
猜测。一个安全状态机应当宁可暂时没有可写主库，也不接受两个相互冲突的写 authority。

简化后的 Patroni 路径：

```text
incumbent loop
  -> renew leader lock
      -> success: remain primary
      -> failure:
           distinguish lock loss from transient DCS error as far as possible
           demote, or enter preconfigured failsafe checks

replica loop
  -> observe no valid leader
      -> verify eligibility and data state
          -> leader race through DCS
              -> winner promotes
              -> others follow the new timeline
```

`ttl`、`loop_wait` 与 `retry_timeout` 会影响探测和选举节奏，但端到端 RTO 还包括
PostgreSQL 停止/恢复、DCS 延迟、候选 replay、代理健康检查、连接重建和客户端重试。

## 33.3.1 候选健康、数据风险与多数判断 {#item-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 可能非常
健康，却绝不能自动成为业务主库。

### 三种“多数”不要混为一谈

```text
DCS quorum
  DCS 自己能否形成一致写 authority

PostgreSQL replicas
  有多少数据节点仍活着、各自有哪些 WAL

business commit quorum
  当前提交策略要求多少同步确认
```

三节点 PostgreSQL 加单节点 etcd，并不会因为“数据库还有 2/3”就拥有 DCS 多数；
三节点 etcd 加两节点 PostgreSQL，也不会自动使 PostgreSQL 写入成为多数派复制。DCS
负责协调 leader authority，WAL durability 由 PostgreSQL replication mode 决定。

生产设计需要分别画失败域：

```text
etcd member placement       odd count, independent failure domains
PostgreSQL placement        required data/availability tolerance
sync policy                 commit latency and data-loss budget
client route                which authority is actually reachable
fence                       how an isolated incumbent loses write ability
```

### 异步可用性换取数据风险

默认异步模式允许 primary 在副本未确认时提交。故障后提升某个满足最低健康条件的
standby，未复制到它的事务会留在旧分支。`maximum_lag_on_failover` 限制候选在最近
观测时的 WAL gap，却不覆盖观测后到故障间的新 WAL。

所以报告应写：

```text
policy lag threshold              1 MiB
observed replay gap at timestamp  exact bytes
last client acknowledgement       token
unknown outcome count             N
post-failover reconciliation      result
```

而不是只写 `RPO < 1MB`。WAL 字节不是业务订单数，unknown 也不等于丢失。

同步模式能收紧正常单故障的数据风险，但也有可用性和复合故障边界。切换报告仍应验证
业务 identity，不把配置名当作结果。

### 自动选择不等于随机选择

Patroni 按当前资格与状态参与 leader race；操作者不应从成员列表顺序推断赢家。本章
正式 run 的两个 replica 都合格，实际 `pg-test-3` 提升。正确验收是：

```text
winner in eligible set
winner is unique running primary
other eligible replica remains streaming
old primary was fenced before acceptance
```

如果业务要求指定节点，使用 planned switchover 或明确 tags/拓扑策略，不能把自动选举
伪装成固定候选。

## 33.3.2 fencing 旧主与防止双写 {#item-33-3-2}

### fence 的目标是消除旧 authority

旧主围栏的验收谓词：

$$
CanWrite(old) = false
$$

或者在特殊设计中：

$$
CanProduceAcceptedEffects(old) = false
$$

第二种更难证明，因为必须覆盖所有客户端、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。

### 顺序不变量

```text
t0 fault action starts
t1 old primary fence independently verified
t2 eligible candidate becomes unique primary
t3 service health accepts new primary
t4 first new-timeline business write acknowledged
```

要求 $t_1 \le t_2$。如果无法观测真实 promotion 瞬间，至少在“接受候选为可服务”
之前完成 fence evidence；不要用事后日志倒推一个未经保护的时间窗不存在。

本章正式结果：

```text
action -> process fence       1.817 s
action -> Patroni stable      4.536 s
fence before stable          true
old service active           false
old postmaster alive         false
old REST reachable           false
```

这是一台可控虚拟机上的 graceful systemd stop。主机断电、内核 hang 与存储 stall
需要不同 fence，不能复用这组时延。

### 双主之后不要急着“选数据多的”

若已经观察到两个 writable primary：

1. 阻断外部写，记录每条路由与 writer；
2. 保存两边 system identifier、timeline、LSN 与业务 identity；
3. 取得至少一边的可信 fence；
4. 由业务 authority 选择权威分支；
5. 隔离提取另一分支独有的合法事实；
6. 用逻辑对账合并，而不是直接让它重新加入；
7. 从权威分支 rewind 被舍弃的一侧，或对其做全量重建。

`pg_rewind` 会舍弃 target 的分叉变化。未先抢救业务事实就 rewind，等于主动销毁可能
需要审计的数据。

## 33.3.3 自动化不确定时何时转人工 {#item-33-3-3}

### 转人工的含义

转人工不是自动执行：

```bash
patronictl failover ... --force
```

它意味着把状态机停在安全边界，要求人补齐：

```text
current accepted authority
old-primary fence mechanism and evidence
candidate identity and lineage
data-loss upper bound / unknown set
service route owner
rollback or rebuild path
production authorization
```

### 三种切换入口

| 入口 | 适用 | leader/candidate | 风险 |
|---|---|---|---|
| automatic failover | 已验证故障与预设策略内 | 由 Patroni/DCS 竞选 | 策略边界内自动 |
| planned switchover | 健康 leader 的维护切换 | 可显式指定 | 可预检、通常低风险 |
| manual failover | leader 不可用且自动路径不能完成 | 必须明确 candidate | 可能放宽 lag/sync 条件并丢数据 |

Patroni REST 文档明确警告 manual failover 可能导致数据损失；无 leader 时，手工候选
可能不受自动 failover 的全部 lag/sync 检查。它是一项风险接受，不是“更强的修复命令”。

Pig 的入口：

```bash
pig pt list pg-test -o json
pig pt switchover --plan
pig pt failover --candidate <member> --plan
pig pt reinit <replica> --plan
```

`--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 只能
证明“在观察位置没看到预期状态”，不能证明旧主已经断电。

### 人工决策记录

```yaml
decision: accept-candidate | retain-incumbent | stop-writes | rebuild
decided_at: UTC
decision_owner: incident-commander
facts:
  - evidence-id
hypotheses:
  - statement-and-test
data_risk:
  last_ack: token
  unknown: manifest
  accepted_loss: explicit
fence:
  target: old-primary
  mechanism: exact
  independently_verified_by: role
stop_condition: exact
rollback: exact
```

没有这份记录的 `--force`，在复盘时无法区分有意识的风险接受与操作失误。

---

[上一节：复制状态与时间线证据](../02/) · [返回本章目录](../) · [下一节：DCS 故障的安全处理](../04/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
