# 实战：同一症状、两种成因

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

---

本实验不给操作者“连接风暴实验”和“WAL 实验”两个有答案的按钮。runner 随机安排
两种根因、生成无语义 case ID，classifier 只能看证据：

```text
common alert: postgresql-resource-headroom-at-risk

case opaque-A:
  connection / retention / engine / filesystem observations

case opaque-B:
  connection / retention / engine / filesystem observations
```

hidden answer 在分类完成后才用于验收。这样练习的是判型，不是背剧本顺序。

## 34.8.1 随机注入连接风暴或 WAL 保留 {#item-34-8-1}

### 先读实验合同

- 环境、阈值与风险：
  [`requirements.json`](/labs/ch34/requirements.json)
- blind classifier 路由：
  [`classification-contract.json`](/labs/ch34/classification-contract.json)
- 34 个反例：
  [`negative-cases.json`](/labs/ch34/negative-cases.json)
- 人类可读边界：
  [`lab-contract.md`](/labs/ch34/lab-contract.md)
- managed/disposable 拓扑：
  [`topology.mmd`](/labs/ch34/topology.mmd)

先做不连接远端的静态检查：

```bash
static/labs/ch34/task.sh lint
```

只读现场采集：

```bash
export PG36_EVIDENCE_DIR="$(
  mktemp -d "${TMPDIR:-/tmp}/pg36-ch34-capture.XXXXXX"
)"
static/labs/ch34/task.sh capture
```

它读取 managed `pg-test` 的 Patroni 成员、primary system identifier/timeline、连接与
replication slot 投影，并确认 `pg-test-3` 没有残留实验 root。它不执行 SQL 写入、
cancel、slot、service、route 或 DCS 变更。

### 完整演练 guard

```bash
export PG36_EVIDENCE_DIR="$(
  mktemp -d "${TMPDIR:-/tmp}/pg36-ch34.XXXXXX"
)"
export PG36_CH34_TARGET=pg36-l2-vagrant/pg-test
export PG36_CH34_NONPRODUCTION=true
export PG36_CH34_PRODUCTION_DATA=false
export PG36_CH34_PRODUCTION_TRAFFIC=false
export PG36_CH34_CONFIRM=BLIND_FLOW_VS_RETENTION_CH34

static/labs/ch34/task.sh drill:overload
```

任一 guard 不符，runner 在创建远端目录前退出。evidence directory 已包含文件也会被
拒绝，避免混合两次 run。

### 隔离引擎

在线 fault 不发生在 managed PostgreSQL，而是在 `pg-test-3` 创建：

```text
/tmp/pg36-ch34-overload-<uuid>/
  .pg36-ch34-owned
  data/
  socket/
  postgres.log
```

关键限制：

```text
PostgreSQL 18.6
listen_addresses=''
Unix socket mode 0700
port 55444 only names the private socket
max_connections=24
superuser_reserved_connections=3
max_replication_slots=4
checksums=on
not a Patroni/DCS/HAProxy/PgBouncer member
```

实验不注入 OOM、不填满文件系统、不 drop cache，也不执行错误动作。无论正常或失败，
只在 marker 与 exact UUID root 匹配后停止临时 postmaster 并删除整个一次性目录。

### 两个真实 fault

FLOW：

```text
30 non-superuser psql clients
same advisory lock
one holder sleeps for 20 seconds
other admitted clients wait on Lock
excess clients hit the connection boundary
```

RETENTION：

```text
one exact inactive physical replication slot
immediately reserved restart_lsn
bounded WAL generation
stop after retained >= 32 MiB
hard cap 128 MiB
```

`max_slot_wal_keep_size=-1` 只在这个一次性实例中用于稳定展示保留机制，绝不是生产推荐
值。

## 34.8.2 在不知道答案时先判型，再选择动作 {#item-34-8-2}

### classifier 唯一允许读取的字段

```json
{
  "case_id": "opaque",
  "observed_at": "UTC",
  "connection": {
    "observed_sessions": 0,
    "connection_rejections": 0,
    "lock_waiters": 0
  },
  "retention": {
    "inactive_physical_slots": 0,
    "retained_wal_bytes": 0
  },
  "engine": {},
  "filesystem": {}
}
```

判定合同：

```text
FLOW =
  observed_sessions >= 18
  AND connection_rejections >= 1
  AND lock_waiters >= 1
  AND inactive_physical_slots = 0

RETENTION =
  inactive_physical_slots = 1
  AND retained_wal_bytes >= 33554432
  AND connection_rejections = 0

both or neither =
  STOP_AND_INVESTIGATE
```

classifier 不读取 `hidden-answers.json`。validator 会比较 case identity set、字段完整性、
classifier provenance 与 hidden answer；blind packet 若带 `truth` 或 `expected_route`
字段反而判失败。

### 正式 run

公开证据：
[`overload-run.json`](/labs/ch34/overload-run.json)

```text
run id         1ae188cd-e7f2-4724-98d8-fe166cf4e33f
random order   RETENTION -> FLOW
```

WAL case 先出现，但 classifier 没把“第一个”解释成 flow：

```text
inactive physical slots     1
retained WAL       42,611,296 bytes
connection rejects          0
route        PRESERVE_RETENTION_EVIDENCE
```

connection case：

```text
attempted clients           30
observed sessions           21
connection rejects           9
lock waiters                20
inactive physical slots      0
route           RELIEVE_FLOW_PRESSURE
```

24 个 `max_connections` 减去 3 个 superuser reserved slots，正好留下 21 个普通 admission；
其余 9 个被拒绝。20 个 lock waiter 加 1 个持锁/睡眠 session 又构成 21 个已进入会话。
这是该临时配置下的可解释闭环，不应外推为任何生产集群连接预算。

## 34.8.3 对流量型恢复服务，对保留型完成安全路由 {#item-34-8-3}

### FLOW 动作

runner 用 run-specific application prefix：

```text
pg36-ch34-flow-<run-prefix>-<client-number>
```

只对匹配 prefix 的 backend 执行 `pg_cancel_backend`。正式结果：

```text
cancel signals sent                    21
broad cancel used                   false
max_connections changed             false
fallback client terminate/kill        0/0
post fixture sessions                   0
post SQL probe                          1
```

若 backend 没在 deadline 内退出，唯一 fallback 是 runner 直接持有的 exact child process；
不会扫描或终止主机上的其他 `psql`，更不会触碰 managed `pg-test`。

生产映射不是“按 prefix 杀 21 个会话”，而是：

```text
identify admission class and owner
stop producer/retry
preserve admin headroom
cancel exact expired/low-value work
verify queue and useful completion
```

### RETENTION 动作

runner 先保存：

```text
slot_name/type/active
restart_lsn
retained_wal_bytes
wal_status/safe_wal_size/invalidation_reason
filesystem projection
```

然后只 drop 带本 run identity 的 disposable slot：

```text
scope                     exact-owned-disposable-slot
evidence preserved        before action
manual pg_wal deletion    false
post physical slots       0
post SQL probe            1
```

生产上的 inactive slot 没有这份授权。对应动作是联系 owner、确认 consumer/backup/RPO，
恢复消费或建立新的恢复起点，再由明确负责人批准 exact release。

### managed 边界复核

before/after 都要求：

```text
pg-test-1 primary running
pg-test-2/3 replica streaming
system identifier unchanged
timeline 19 unchanged
chapter fixture sessions 0
matching disposable roots []
```

公开报告因此写的是 `managed mutations=0`，而不是声称在 Pigsty managed cluster 上
验证了饱和行为。

## 34.8.4 输出动作时间线、误判代价与容量改进项 {#item-34-8-4}

### evidence bundle

```text
before.json
exercise/
  run-manifest.json
  source-manifest.json
  exercise-evidence.json
  blind-packets.json
  hidden-answers.json
  cleanup.json
classification.json
after.json
validation-report.json
negative-report.json
public-summary.json
review.txt
```

已有完整证据包可重复做只读校验：

```bash
export PG36_EVIDENCE_DIR=/absolute/evidence/ch34/run
static/labs/ch34/task.sh all
```

validator 将 12 个实验 source file 与 SHA-256 绑定，并实际构造 34 个 mutant。反例覆盖：

```text
production/managed mutation guard opened
scenario or classifier contract weakened
blind packet leaks truth or misses fields
classification duplicated/unsupported/wrong
flow evidence below threshold or broad cancel
retention slot/bytes/active state falsified
manual WAL deletion or leftover slot claimed
cleanup root left behind
production gate falsely approved
```

34 个都必须被拒绝；声明有 34 条 JSON 并不等于做了对抗验证。

### 误判代价

| 误判 | 结果 |
|---|---|
| flow 当 retention | 不限制 admission，连接失败与队列继续 |
| retention 当 flow | cancel session 不推进 `restart_lsn`，WAL 继续增长 |
| unknown 强判 flow | 可能 terminate 关键/大事务，并留下既有 WAL、死版本与后续清理压力 |
| unknown 强判 retention | 可能 drop 有效 slot、破坏 consumer/RPO |
| 手工删 `pg_wal` | 破坏 crash recovery、复制、备份与现场证据 |

因此 unknown route 不是实验的“第三种错误答案”，而是证据不足时唯一正确的动作类别。

### 从实验回写容量控制

这次结果至少导出四个可验证改进：

1. 为普通连接显式保留 admin/reserved budget，并让 pool 上限小于硬边界；
2. 对 application pool 总和、retry 与启动 prewarm 做全局预算；
3. 对每个 slot 记录 owner、active、restart/catalog xmin、retained bytes 与推进率；
4. 告警同时包含“当前余量”和“增长率/耗尽时间”，并带 FLOW/RETENTION 判型链接。

不能从这次实验导出：

```text
production max_connections should be 24
every inactive slot may be dropped after 32 MiB
42 MB WAL implies a disk incident
exact cancel always has zero fallback
managed Pigsty can tolerate the same storm
```

最终门禁保持：

```text
production_ch34_gate=pending
```

---

[上一节：平台级流量控制与证据](../07/) · [返回本章目录](../) · [下一章：数据抢救与工程取证——起死回生](/data-rescue-forensics/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
