# CPU、内存、I/O 与 OOM

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

---

数据库资源彼此耦合。内存紧张会增加 reclaim 与 swap I/O；I/O 变慢会延长 query 和
transaction，占住更多连接与内存；连接排队又会触发超时重试，进一步增加 CPU。
事故中不能把每张主机图分开解释，而要寻找同一时间线上的因果方向。

## 34.4.1 饱和、排队、抖动与抢占 {#item-34-4-1}

### 利用率不是完整答案

CPU 100% 可能仍有高吞吐且尾延迟可接受；CPU 40% 也可能因为单核热点、锁、自旋、
steal time 或 I/O 等待导致业务停滞。主机证据至少包括：

```text
per-CPU user/system/iowait/steal
run queue and runnable tasks
context switches
memory pressure / reclaim / swap
per-device latency, queue depth, throughput and errors
cgroup/container limits and throttling
filesystem free bytes and inodes
```

再按 PostgreSQL backend type 与 wait event 对齐：

```sql
SELECT backend_type,
       wait_event_type,
       wait_event,
       count(*) AS processes
FROM pg_stat_activity
GROUP BY 1, 2, 3
ORDER BY processes DESC;
```

PostgreSQL 官方建议把统计视图与操作系统工具结合，因为数据库 I/O 统计不能区分数据
来自物理设备还是内核 page cache。数据库看到 read，也不等于磁盘实际发生同量读取。

### 饱和、排队和抖动

```text
saturation
  resource has little service headroom

queueing
  work waits before resource service

jitter
  completion latency varies sharply over time

contention/preemption
  work loses CPU/lock/device service to other work
```

平均设备延迟 2 ms 可能掩盖 checkpoint 时 500 ms 尖峰；五分钟 CPU 平均值也会抹掉
每 30 秒同步到来的任务。保留原始采样粒度、时钟和分位数，不要只截一张平滑后的图。

### 先区分主机级还是数据库级

| 证据组合 | 更可能的方向 |
|---|---|
| host run queue 高，PG 多数无 wait | CPU runnable 竞争 |
| PG 大量 `Lock`，CPU 不高 | 数据库锁序列化 |
| device await/queue 高，PG 大量 `IO` | 存储服务能力不足 |
| cgroup throttled，宿主机空闲 | 容器/服务配额 |
| swap/reclaim 高，连接与 query 同增 | 内存承诺或并发过大 |
| PG 平稳，其他进程占资源 | noisy neighbor/备份/扫描 |

不要在没确认 cgroup/虚拟化边界时用宿主机总容量推导数据库余量。

## 34.4.2 临时文件、并行、checkpoint 与后台维护 {#item-34-4-2}

### 前台与后台会争同一设备

下面工作可能同时写盘：

```text
sort/hash spill -> temp files
WAL writer / WAL sync
backend data writes
checkpointer flushing dirty buffers
autovacuum/vacuum
CREATE INDEX / REINDEX
base backup and archive
operating-system or storage maintenance
```

`pg_stat_io` 按 backend type、object 与 context 提供集群级 I/O 统计；`pg_stat_database`
的 temp 计数、日志中的 temporary file、`pg_stat_checkpointer`、`pg_stat_archiver` 和
进度视图分别补充来源。统计是累计量，需要记录起点并取差值：

```sql
SELECT backend_type,
       object,
       context,
       reads,
       read_time,
       writes,
       write_time,
       extends,
       fsyncs,
       fsync_time
FROM pg_stat_io
ORDER BY backend_type, object, context;
```

列集合随 PostgreSQL 版本演进，生产脚本应绑定 major version 并做兼容检查。

### 临时文件是结果，不是单一根因

spill 可能来自：

- `work_mem` 对该 sort/hash 太小；
- 行数估计错误导致计划不合适；
- 并发相同操作太多；
- 查询本来就必须处理大量数据；
- hash 操作按 `hash_mem_multiplier` 获得更高上限；
- parallel workers 各自执行内存/临时工作。

直接把 `work_mem` 全局放大，可能把磁盘事故变成 OOM。优先修 query/统计、限制该工作
并发，必要时只对可控 role/session 做有界调整并验证。

### checkpoint 峰值与追赶效应

checkpoint 需要把脏页推进到 durable storage。写流量突增、WAL 配置、恢复/重启后的
缓存重新填充和存储变慢都可能让 checkpoint 与前台 I/O 相互干扰。事故中记录：

```text
checkpoint requested/timed
buffers written and write/sync duration
WAL generation rate
device write latency/queue
replica and archive progress
```

暂停 autovacuum 或 checkpoint 通常不是通用止血。autovacuum 还承担 XID freeze；
暂停后可能把短期 I/O 压力转成更危险的保留问题。只能对已识别对象、在明确时间窗与
回补计划下调整维护。

## 34.4.3 内存最坏并发、OOM killer 与进程重启 {#item-34-4-3}

### `work_mem` 不是每连接只分配一次

PostgreSQL 文档强调，`work_mem` 是一个 query operation（如 sort/hash）的基础上限；
一个复杂 query 可同时有多个 operation，多个 session 又可并发，parallel worker 也会
扩大总使用。粗略上界应按工作节点估算：

$$
M_{\text{workload}}
\approx
\sum_{s \in \text{active sessions}}
\sum_{o \in \text{concurrent operations}(s)}
\sum_{w \in \text{participants}(s,o)}
M_{s,o,w}
$$

其中 `participants` 包括执行该 operation 的 leader 与 parallel workers。这个表达式的
关键不是算出一个恒定值，而是把“同时活跃的 session × 同时活跃的内存节点 × 参与
进程”三层并发都纳入预算。

再加上：

```text
shared_buffers and shared memory
backend base memory
maintenance_work_mem / autovacuum_work_mem
logical decoding and extension memory
kernel page cache
proxy, exporter, Patroni and other host processes
failure/recovery reserve
```

因此：

```text
max_connections * work_mem
```

既不是准确实测，也不是足够保守的最坏值。它漏掉每 query 多个节点和并行，也忽略许多
非 work_mem 内存；反过来假设所有连接同时打满每个上限又可能极度悲观。容量测试要用
真实 workload envelope 和并发组合。

### Linux OOM 不是数据库的流控机制

Linux overcommit 允许进程承诺超过物理内存的虚拟地址空间；真正耗尽时，OOM killer
可能选择某个进程。若 PostgreSQL child 被杀，postmaster 会把它当作异常退出，为保护
共享内存一致性，可能终止其他 server processes 并执行 crash recovery；若 postmaster
本身被杀，服务管理器行为又是另一条路径。

不要故意在共享/生产主机上“测一次 OOM”。安全实验应在有明确 cgroup/VM 边界的专用
环境里完成，并验证：

```text
which cgroup/host reported OOM
which PID and backend_type was selected
whether postmaster remained
whether crash recovery occurred
client unknown outcomes
replica/archive/backup state after restart
```

本章正式实验明确不注入 OOM。

### 事故动作

内存压力下优先：

1. 阻止新低价值工作和重试；
2. 识别 exact 高内存 query/role/pool；
3. 暂停可恢复 batch 与并行任务；
4. 用 query cancel 逐步释放，而不是一次 terminate 全部；
5. 保留管理连接与 OS 控制面；
6. 观察 reclaim、swap、RSS、队列和业务成功率；
7. 稳定后修正连接预算、query、并行与内存配置。

不要在事故中 drop OS cache：它既不能修复内存承诺，还会把后续读取推向存储，破坏
现场并制造新的 I/O 峰值。也不要把 swap 使用本身等同于故障；关键是持续 swap in/out、
memory pressure 与用户影响。

### 资源证据矩阵

```yaml
cpu:
  utilization: ...
  run_queue: ...
  steal_or_throttle: ...
memory:
  available: ...
  pressure: ...
  swap_rate: ...
  oom_event: ...
io:
  device_latency_queue: ...
  pg_waits: ...
  checkpoint_temp_maintenance: ...
workload:
  admitted_running_waiting_rejected: ...
  root_queries_or_jobs: ...
decision:
  exact_control: ...
  stop_and_rollback: ...
```

单张 `top` 截图不够支撑数据库重启。

---

[上一节：失控查询、锁与事务](../03/) · [返回本章目录](../) · [下一节：流量型止血动作](../05/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
