# 先识别失败域

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

---

“连接失败”是用户看到的症状，不是失败域。它可能来自 DNS、证书、连接池、HAProxy、
主库进程、整台主机、磁盘阻塞或 DCS；这些故障需要完全不同的动作。没有定位失败域就
执行 promotion，相当于用一次有数据风险的拓扑变更修一个尚未证明属于数据库的问题。

先画当前请求真正经过的路径：

```text
application
  -> DNS / VIP
      -> HAProxy
          -> PgBouncer
              -> PostgreSQL socket / TCP
                  -> primary process
                      -> storage

Patroni -> DCS leader lock
Patroni REST -> HAProxy health decision
primary -> WAL sender -> receiver -> replay
```

每一条箭头都可以失败。事故调查应从失败请求出发逐跳验证，同时从 PostgreSQL 和
Patroni 反向确认角色，最终在同一 UTC 时间线上汇合。

## 33.1.1 进程失败、主机失败与存储失败 {#item-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 四处取得“不能写”证据。本章实验就是这种窄模型：

```text
systemctl is-active patroni    -> inactive
managed postmaster PID         -> absent
Patroni REST                   -> unreachable
direct SQL                     -> unavailable
```

这证明的是 process fence，不是 hardware fence。

主机不可达更危险。`ping` 超时可能是 ICMP 被过滤，SSH 超时可能只是管理网故障，云 API
显示 running 也不说明内核还能调度 PostgreSQL。若无法从故障主机自身证明停机，应使用
独立控制面：

- BMC/IPMI 或电源控制器断电；
- 云或虚拟化平台 stop/fence，并等待实例状态与网络状态收敛；
- 存储租约或卷 detach，保证旧主无法继续访问可写数据；
- 硬件/软件 watchdog 在 Patroni 失去心跳时使节点重启；
- 经审计的网络 fence，阻断所有写入路径而不只是一条客户端路由。

“我连不上它”不是 fence evidence。一个从管理网不可达、从业务网仍可服务的旧主，正是
最典型的脑裂来源。

### 存储故障不一定表现为进程退出

存储卡顿时 postmaster、REST 和端口都可能仍在，健康检查却因超时把节点摘除。要同时
检查：

```text
kernel / filesystem error
device latency and queue depth
PostgreSQL wait events and checkpoint latency
Patroni loop delay
DCS lease update latency
WAL archive and replica sender progress
```

若进程因 I/O hang 无法及时 demote，单纯 `systemctl stop` 也可能卡住。此时需要 watchdog
或外部 power/storage fence。不要为了让命令“成功返回”直接 `kill -9` Patroni；杀掉
控制进程并不自动杀掉仍可写的 postmaster，反而可能移除最后一层角色管理。

### 先写可证伪假设

```yaml
hypothesis: PostgreSQL process failure on pg-test-1
supports:
  - host SSH reachable
  - patroni service inactive
  - postmaster PID absent
  - REST and direct SQL unavailable
contradicts:
  - kernel I/O errors
  - host power state unknown
not_claimed:
  - host is powered off
  - storage is healthy
stop_line:
  - any evidence that old postmaster still accepts writes
```

如果反证出现，就回到失败域判断，不要硬把它解释成预期剧本。

## 33.1.2 网络分区、客户端不可达与代理误判 {#item-33-1-2}

### “数据库可用”有多个观察位置

至少分开下面四个探针：

```text
local SQL       node-local socket -> PostgreSQL
direct SQL      observer -> exact PostgreSQL address
service SQL     application subnet -> HAProxy/PgBouncer/VIP
business write  real auth/transaction/idempotency path
```

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 也可能被当成写后端。

审阅时写出：

```text
frontend address and port
backend health URL and expected status
check interval / rise / fall
connection-drain behavior
PgBouncer transaction/session mode
DNS/VIP convergence
application retry and target_session_attrs
direct-IP escape routes
```

### 网络分区没有单一“网络坏了”

用方向矩阵描述：

| 来源 → 目标 | 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 可能绕过它：

```text
direct host:port connection
maintenance job on database host
logical replication subscriber callback
scheduler or ETL
monitoring remediation script
another region's proxy
cached DNS or stale VIP owner
```

服务面隔离是必要条件，却不能替代数据面的 stop/watchdog/power/storage fence。

## 33.1.3 主库失败、复制停滞与控制面失败 {#item-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。为了让“拓扑看起来
整齐”而切主，会给业务增加一次没有必要的中断。

### 控制面状态与数据库事实要成对记录

一个最小事实包：

```text
UTC and monotonic timestamps
Patroni members: role / state / timeline / lag / tags
dynamic config: pause / ttl / loop_wait / retry_timeout
DCS: leader key identity / revision / latency / auth result
SQL per node: pg_is_in_recovery / system_identifier / timeline / LSN
OS per node: service / PID / I/O / clock
service path: backend selected / health result
client: exact token and outcome
```

不要只贴 `patronictl list` 截图。DCS 故障时 CLI 自己可能无法读取状态；SQL 能显示本机
角色，却不知道它是否仍持有 authority；代理能显示后端健康，却不知道数据 lineage。
相互矛盾正是事故信号，不能挑一个最方便的来源当真相。

### 五条立即停手线

出现任一项，自动动作转人工：

1. 旧主是否仍可写为 unknown；
2. 观察到两个 writable PostgreSQL；
3. 候选 system identifier 或 timeline history 不明；
4. DCS 权威分区与数据库可达分区不一致；
5. 无法说明客户端 unknown transaction 如何对账。

转人工不是“直接运行 manual failover”。它意味着冻结进一步自动化、保留当前事实，由
事故指挥人明确风险、候选、fence、回退和业务 owner，再授权下一动作。

### 本节检查表

进入候选选择前，应能完成：

```yaml
failure_domain: process | host | storage | network | proxy | replication | dcs
affected_paths: [...]
incumbent_sql_role: fact-reference
incumbent_authority: valid | fenced | unknown
candidate_set: [...]
lineage_status: same-system-id-and-history | unknown
client_unknown_outcomes: count-and-reconciliation-owner
contradicting_evidence: [...]
next_action: bounded-and-reversible
stop_condition: explicit
```

下一节将把其中的 replication 与 timeline evidence 展开。

---

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