# 第一动作：流量型还是保留型

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

---

资源告警出现后，先不要问“删什么”或“重启谁”，而要问：

> 当前资源是被**正在到达和执行的工作**消耗，还是被一个**不能前进的历史边界**
> 保留？

这不是给故障贴标签，而是选择安全动作。流量型压力需要减少进入量、并发或单项成本；
保留型压力需要找到保留者及其 owner。两类证据都成立时，先处理即将触发的硬失败，
同时保留另一条根因链；两类都不成立时，保持 unknown。

## 34.1.1 流量增长、慢查询、锁与连接导致的竞争 {#item-34-1-1}

### 流量型的共同结构

下面四种表象最后都会形成“到达大于完成”：

| 放大源 | 到达侧变化 | 完成侧变化 |
|---|---|---|
| 业务流量增长 | 请求数增加 | 单次成本可能不变 |
| 慢查询或坏计划 | 请求数不变 | 每次占用 CPU/I/O/连接更久 |
| 锁竞争 | 等待工作继续占连接 | 有效并行度下降 |
| 连接风暴 | 建连、认证、backend 创建增加 | 正常查询拿不到 admission |

若每秒进入 $\lambda$ 个工作、完成 $\mu$ 个工作，持续满足 $\lambda>\mu$，队列就增长。
这里的工作可以是 HTTP 请求、池等待者、数据库 session、正在运行的 statement 或磁盘
I/O。只看 PostgreSQL 的 active session 会漏掉在应用池和代理前排队的请求。

先把同一 UTC 窗口的证据放在一起：

```sql
SELECT application_name,
       state,
       wait_event_type,
       wait_event,
       count(*) AS sessions,
       max(clock_timestamp() - coalesce(xact_start, query_start))
         AS oldest_age
FROM pg_stat_activity
WHERE backend_type = 'client backend'
GROUP BY 1, 2, 3, 4
ORDER BY sessions DESC;
```

再对照：

```text
application arrival / timeout / retry
pool waiting clients / server connections
proxy accept / queue / backend health
PostgreSQL active / idle-in-transaction / Lock waits
host run queue / memory pressure / device latency
```

`state='active'` 不表示正在使用 CPU；PostgreSQL 文档明确指出 `state` 与 `wait_event`
相互独立。active 且 `wait_event_type='Lock'` 的 backend 正在执行语句，但实际被阻塞。
这也是为什么“active 数很多”必须继续拆成 running、lock wait、I/O wait 与 client wait。

### 判定成立的最低条件

把问题判为 flow，至少应看到一条能够闭合的因果链：

```text
arrival/retry increases
  -> admission or execution concurrency increases
  -> queue/wait/rejection grows
  -> completion rate or user success falls
```

仅凭 CPU 90% 不够。CPU 高也可能是 checkpoint 后的恢复工作、压缩、备份或一个与用户
延迟无关的后台任务；连接数高也可能都是长期 idle、但尚未达到瓶颈。

## 34.1.2 WAL、XID、复制槽、归档和长事务导致的保留 {#item-34-1-2}

### 保留型不是“有人正在大量使用”

PostgreSQL 为恢复、复制和 MVCC 正确性保留历史。只要某个消费者仍声明“我可能需要
这里以前的内容”，系统就不能越过它回收：

| 被保留对象 | 常见保留者 | 关键边界 |
|---|---|---|
| 旧 tuple 版本 | 活跃快照、长事务、prepared transaction | `backend_xmin`、prepared XID |
| catalog tuple | logical replication slot | `catalog_xmin` |
| WAL segment | physical/logical slot、备库、备份 | `restart_lsn` |
| 待归档 WAL | archive command/repository 失败 | `pg_stat_archiver` 与归档队列 |
| 事务 ID 安全空间 | 未冻结表与被钉住的 xmin | relation/database age |

这里重要的是**最老边界及其速度**：

```sql
SELECT pid,
       usename,
       application_name,
       state,
       xact_start,
       backend_xid,
       backend_xmin
FROM pg_stat_activity
WHERE backend_xid IS NOT NULL
   OR backend_xmin IS NOT NULL
ORDER BY xact_start NULLS LAST;

SELECT slot_name,
       slot_type,
       active,
       xmin,
       catalog_xmin,
       restart_lsn,
       wal_status,
       safe_wal_size,
       invalidation_reason
FROM pg_replication_slots
ORDER BY slot_name;

SELECT transaction, gid, prepared, owner, database
FROM pg_prepared_xacts
ORDER BY prepared;
```

inactive slot 不等于废弃 slot。它可能对应暂时离线的副本、迁移、CDC 消费者或恢复流程；
active slot 也不等于健康，消费者可能连着却不推进。必须把 slot 映射到服务 owner、
consumer、恢复承诺和最后成功时间。

### 先测增长，再谈释放

两个快照比一个快照更有意义：

```text
t0: free bytes, current LSN, restart_lsn, archive failure count
t1: same fields after a known interval

retained bytes     = current LSN - restart_lsn
growth rate        = (retained_t1 - retained_t0) / elapsed
time to hard limit = usable headroom / positive growth rate
```

若 `max_slot_wal_keep_size=-1`，replication slot 可以不受该参数上限地保留 WAL；即使配置
了有限值，相关状态也在 checkpoint 时才重新评估，不能把参数值误当作实时保险丝。

## 34.1.3 同样表现为“磁盘满”或“延迟高”，动作可以相反 {#item-34-1-3}

### 症状相同，控制变量不同

| 症状 | 可能的 flow 根因 | 可能的 retention 根因 | 需要区分的证据 |
|---|---|---|---|
| `pg_wal` 大 | 写流量突增、checkpoint 压力 | slot/归档/备份钉住 WAL | WAL 生成率与最老保留 LSN |
| 表膨胀 | 更新/删除量增长 | 长快照或 slot `catalog_xmin` | DML 率、vacuum 进度与 xmin |
| 磁盘延迟高 | 并发查询、temp、checkpoint | 被保留数据持续占满并触发写放大 | device queue、文件分类、增长率 |
| 连接失败 | 到达/重试超过连接预算 | 磁盘满后新事务无法写 WAL | pool queue、PG error、filesystem |
| CPU 高 | 执行/解析/自旋竞争 | recovery/cleanup 追赶保留积压 | backend type、wait、工作量变化 |

因此动作可以完全相反：

```text
flow:
  stop admission -> cancel exact work -> queue falls

retention:
  preserve owner/lineage evidence -> repair consumer/archive
  -> only then release an exact, authorized boundary
```

把 retention 当 flow，取消再多普通查询也不会推进 `restart_lsn`。把 flow 当 retention，
忙着调查 slot 而不限制重试，服务可能先被连接和内存击穿。

### 同时发生怎么办

WAL slot 滞留期间又发生写入洪峰并不矛盾。事故记录应允许：

```yaml
classification:
  flow: confirmed
  retention: confirmed
immediate_hard_failure: filesystem-full-in-18m
parallel_controls:
  - reduce noncritical write admission
  - preserve slot/archive evidence and contact owner
forbidden:
  - delete pg_wal files
  - drop unknown slot
```

“只能选一个根因”是复盘分类，不是在线处理原则。

## 34.1.4 判型不清时先停止破坏性清理 {#item-34-1-4}

### unknown 是一种有效状态

下面任一项不明，就不能执行不可逆释放：

```text
which path is failing?
which object is growing?
who owns the oldest xmin/restart_lsn?
is there a valid backup or replica copy?
what client work will be canceled?
what data or recovery capability can be lost?
```

先收集最小证据包：

```text
UTC + monotonic timestamp
user-facing probe and exact error class
pg_stat_activity grouped by app/state/wait
blocker tree and long/prepared transactions
pg_replication_slots and pg_stat_archiver
current/replay LSN and write rate
filesystem by mount/path plus inode usage
CPU/memory/pressure/device queue
recent config/deploy/failover/backup changes
```

然后将路线写成机器与人都能审阅的三态判定：

```text
FLOW
  sufficient connection/queue/wait evidence
  and no contradictory retention evidence for this action

RETENTION
  exact owner boundary and retained quantity observed
  and flow relief cannot release that boundary

STOP_AND_INVESTIGATE
  两类证据同时成立，或任何一类都不足以支持动作
```

停止线包括：有人建议删 `pg_wal`、drop 未知 slot、终止未知大事务、在唯一副本上试验
危险参数、或以“磁盘快满”为由跳过 owner/backup 识别。此时应保护现场并回到第 31 章
的事故指挥框架。

### 本节交付物

进入具体止血前，至少写出：

```yaml
symptom: exact user and resource observation
scope: service / database / node / time window
classification: FLOW | RETENTION | BOTH | UNKNOWN
supporting_evidence: [...]
contradicting_evidence: [...]
first_action: bounded and reversible
stop_condition: measurable
owner: named role
```

没有这些字段，“先重启看看”不是 runbook。

---

[返回本章目录](../) · [下一节：连接风暴与排队失控](../02/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
