# 从症状路由而不是猜根因

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

---

事故初期通常只能看到症状。路由的目的不是在十五分钟内宣布 root cause，而是把事件
交给一组**不会立即破坏恢复条件**的下一步协议。当前证据足以回答主要响应目标，就可以
先进入对应章节；新证据矛盾时再返回本节重新判型。

先填一行：

```text
symptom       what was directly observed
impact        user/data/recovery consequence
known copies  primary / replicas / backup / archive / clone
uncertainty   writer / timeline / resource / integrity
next route    PITR / HA / OVERLOAD / INTEGRITY
stop line     action forbidden until which fact is known
```

## 31.3.1 误删误改与恢复目标 → ch32《PITR 与误操作恢复》 {#item-31-3-1}

### 什么时候进入数据恢复路线

下列证据把问题指向 ch32：

- 已提交的 DML、DDL、job 或业务事件使数据变成错误状态；
- primary 和 physical replicas 都忠实包含同一错误；
- 需要从某个历史时间、XID、LSN 或 restore point 重建旧状态；
- 当前正确写入仍在发生，不能简单让全库倒退；
- 备份与 WAL archive 是否覆盖目标边界尚需证明。

最常见的误判是“副本没延迟，所以可以提升”。物理复制正是把 WAL 中的错误
`DELETE`/`UPDATE` 重放到副本；零延迟只说明错误传播完成。

### 先固定三个边界

```text
damage start       首个错误事务可能开始的边界
damage end         错误 writer 被围住或最后错误提交的边界
recovery target    期望隔离恢复停止的 time / XID / LSN / restore point
```

告警时间通常晚于 damage start；应用日志时间还可能受时区和时钟漂移影响。优先用事务
审计、DDL log、job run id、业务 marker 和 WAL/LSN 交叉定位。PostgreSQL 连续归档可以
按 time、XID、LSN 或 named restore point 恢复，并会创建新的 timeline
（[PITR](https://www.postgresql.org/docs/18/continuous-archiving.html)）。

PITR 恢复的是一个 PostgreSQL 集群状态，不是原地“撤销一条 SQL”。生产通常需要：

```text
保留现役提交
  + 隔离恢复到目标
      + 提取受影响对象或比较 manifest
          + 处理 damage end 之后的合法写入
              + 条件化回灌或整体切换
```

### 进入 ch32 前先保护输入

- 围住精确的错误 job、角色或业务写路径；
- 记录 source system identifier、timeline、当前 LSN 与目标时区；
- 确认 base backup、archive min/max、失败记录和 retention owner；
- 禁止清理 WAL、删除备份或覆盖现役主库；
- 枚举 target-only 合法提交与外部副作用；
- 指定业务数据 owner，数据库团队不能代替业务裁决旧值。

若归档覆盖未知，不要先启动一次随意恢复来“试试看”；先把现有 repo 和 WAL 保留下来，
再在独立目标验证。具体恢复目标、timeline 和回灌协议见
[第 32 章](/pitr/)。

## 31.3.2 主节点、复制或 DCS 异常 → ch33《故障切换与集群重建》 {#item-31-3-2}

### 从四个观察面证明拓扑

“primary down”至少要拆成：

| 观察面 | 关键事实 |
|---|---|
| 客户端与服务 | DNS/VIP/HAProxy/PgBouncer 实际送到谁，旧连接还在哪 |
| PostgreSQL | `pg_is_in_recovery()`、system identifier、timeline、LSN、可否提交 |
| Patroni 与 DCS | member、leader lock、租约、pause/failsafe、候选状态 |
| 网络与主机 | 节点是否存活、分区方向、存储 lease/fencing 是否成立 |

四层结论不一致时，优先把它当作 writer identity 风险，而不是选择“看起来最新”的节点
提升。特别是旧 primary 与 DCS 多数派隔离时，它可能仍接受直连或遗留 VIP 流量。

PostgreSQL 核心只提供 standby、promotion 与复制机制，不负责完整的故障检测和通知；
官方 failover 文档强调旧 primary 必须有机制知道自己不再是 primary，通常称为
STONITH/fencing，以避免两个节点都认为自己可写
（[PostgreSQL Failover](https://www.postgresql.org/docs/18/warm-standby-failover.html)）。
在 Pigsty 中应由 Patroni/DCS 与声明的服务机制协调，而不是绕过控制面随意
`pg_ctl promote`。

### failover 前的最低证明

```text
candidate       system id matches; role and replay state known
data boundary   receive / flush / replay LSN and expected RPO
old writer      stopped or independently fenced
DCS             quorum and leader ownership understood
routing         exact service owner and drain behavior known
archive/slots   promotion后的归档、physical/logical slot continuity assessed
rollback        old primary rejoin requires rewind or rebuild, not直接开机
```

同步复制提高已确认事务的耐久承诺，但仍要根据当时 `sync_state`、配置和客户端收到的
commit acknowledgment 判断；“lag 面板为 0”不是 fencing 证明。

若观察到两个 timeline 都有独占提交，事件已经同时包含数据 reconciliation。先保存两边
证据，不能用 `pg_rewind` 把旧分支直接覆盖。`pg_rewind` 官方也警告失败后的 target
数据目录可能不可恢复，应改用新备份
（[`pg_rewind`](https://www.postgresql.org/docs/18/app-pgrewind.html)）。

完整的候选选择、切换、fencing、旧主重建与服务验收见
[第 33 章](/failover-rebuild/)。

## 31.3.3 连接、延迟、CPU、内存、I/O 或磁盘表象 → ch34《过载保护与资源故障判型》 {#item-31-3-3}

### 可用性故障不一定是 HA 故障

下列症状优先进入资源判型：

- connection timeout，但 primary/route 身份一致；
- pool wait、`max_connections`、worker 或 thread pool 达上限；
- TPS 下降伴随 lock、client、I/O、WAL flush 等 wait；
- CPU run queue、内存回收/OOM、I/O latency 或文件系统空间异常；
- `pg_wal`、temp、日志、backup 或 telemetry retention 增长；
- 单一查询、租户、job 或重试风暴占据大部分资源。

PostgreSQL 官方建议把累计统计与 `ps`、`top`、`iostat`、`vmstat` 等 OS 观察结合，
因为数据库视图不能完整描述 kernel page cache、设备和调度器
（[Monitoring Database Activity](https://www.postgresql.org/docs/18/monitoring.html)）。

### 按资源链逐层问

```text
admission   请求量、重试、HAProxy queue、PgBouncer wait
sessions    connection slots、idle in transaction、transaction age
contention  locks、buffer pin、WAL/sync、client waits
compute     CPU、run queue、memory、swap、OOM、NUMA
storage     latency、queue depth、throughput、filesystem/inode
persistence checkpoint、WAL generation、archive、slots、replica replay
```

单个指标不能宣布根因。CPU 43% 时连接槽仍可耗尽；磁盘吞吐未满时 fsync tail latency
仍可拖慢 commit；`ClientRead` 可能表示 backend 等客户端，而不是数据库内部繁忙。
`state` 与 `wait_event` 也是独立字段，官方提醒两者之间可能出现短暂不一致。

### 先保住管理面和剩余容量

优先动作通常是：

- 在入口停止异常 deploy、批任务或非关键重试；
- 保留管理连接，设置诊断查询超时；
- 分批 cancel 精确 query，必要时再 terminate 已复核 session；
- 限制新工作而不是把 `max_connections` 一路调大；
- 扩容或释放**已声明的应急预留**，不手删数据库文件；
- 对 slot、archive、replica 和 backup 分别确认 owner 后再改变保留。

Pigsty 故障指南把数据、`pg_wal`、日志、本地备份、监控数据和对象存储列为不同的空间
来源
（[Pigsty Troubleshooting](https://pigsty.io/docs/pgsql/tutorial/failure/)）。先用
`df` 确认文件系统，再按目录类别和增长率定位；`du` 很重时也要限制范围和优先级。
绝不能直接删除 `pg_wal` 中看似旧的文件。

过载中的负载保护、连接排空、磁盘急救、内存与 I/O 判型见
[第 34 章](/overload-resource-incidents/)。

## 31.3.4 checksum、索引、排序或逻辑不一致 → ch35《数据抢救与工程取证》 {#item-31-3-4}

### 先区分“不一致在哪一层”

| 证据 | 主要回答 | 不能单独证明 |
|---|---|---|
| data checksum | 读取到的物理 page 是否匹配页内 checksum | 业务值正确、所有页都已读 |
| `amcheck` | B-tree/heap 结构的特定不变量 | 存储设备健康、业务副本等价 |
| collation version + dependency | 排序 provider 版本与依赖对象范围 | 索引已经重建、冲突值已裁决 |
| seq/index result comparison | 某查询路径是否出现差异 | 全库无其他损坏 |
| business manifest | 行数、摘要、外键、金额等业务不变量 | page/WAL 物理完好 |
| backup restore | 某份备份能恢复并读取声明对象 | 当前 primary 正确、其他时间窗可恢复 |

任何一项失败，都先记录 database、relation、fork、block、timeline、LSN、查询路径和
首次时间。任何一项通过，也只缩小它所覆盖的故障面。

### 修复会覆盖原因

发现 index scan 漏行时立刻 `REINDEX`，可能恢复服务，却同时丢掉：

- 修复前 seq/index 集合；
- 原 relfilenode 与受影响 block；
- collation 版本和升级动作顺序；
- 存储错误、checksum 与其他副本对照；
- 业务上冲突键的裁决机会。

更安全的顺序是：

```text
围住受影响写路径
  -> 保存原始存储或一致快照
      -> 建立对象与业务范围映射
          -> 在工作副本运行分层检测
              -> 比较独立副本和备份
                  -> 才选择重建、提取、回灌或整体替换
```

`amcheck` 提供 B-tree 检查和 `verify_heapam`，但不同函数的锁、代价与误报/漏报边界不同，
应按版本阅读
[`amcheck`](https://www.postgresql.org/docs/18/amcheck.html) 文档并先在克隆测量。
`pg_checksums --check` 要求集群 cleanly shut down，不能在现役 primary 上临时跑
（[`pg_checksums`](https://www.postgresql.org/docs/18/app-pgchecksums.html)）。

若一个 page 的候选来自 replica 或备份，仍不能直接复制进 live data file；WAL、tuple
visibility、FSM/VM、索引和业务约束可能不在同一边界。具体的隔离、抢救、证据链和
重建协议见[第 35 章](/data-rescue-forensics/)。

---

[上一节：第一原则：保护现场与可恢复性](../02/) · [返回本章目录](../) · [下一节：决策、沟通与变更纪律](../04/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
