# 连接风暴与排队失控

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

---

PostgreSQL 采用一个 client connection 对应一个 backend process 的模型。连接不仅占一个
数字，还需要进程、内存、认证、catalog 初始化、socket、锁表与调度成本。连接池的
价值不是让数据库接受无限请求，而是把大量 client concurrency 变成有上限的 database
concurrency。

## 34.2.1 数据库连接、代理池与应用池三层 {#item-34-2-1}

### 三层都在排队

```text
request
  -> application worker / local pool waiters
      -> PgBouncer client connections / waiters
          -> PgBouncer server connections
              -> PostgreSQL client backends
```

每层至少有四个量：

```text
admitted
running
waiting
rejected/timed out
```

只看 PostgreSQL `numbackends` 会漏掉池前的队列；只看应用 pool size 又会漏掉多个
pod/进程/租户汇总后对数据库的总承诺。

一个粗略预算应满足：

$$
\sum_i A_i P_i + M + B \le C_{\text{pool}}
$$

其中 $A_i$ 是第 $i$ 类应用实例数，$P_i$ 是每实例可能占用的 server connection，
$M$ 是迁移、运维和监控预算，$B$ 是故障切换/伸缩缓冲。对 PostgreSQL：

$$
C_{\text{pool}} + C_{\text{direct}} <
\texttt{max\_connections} - C_{\text{reserved}}
$$

这里不是要求把所有层的上限设成同一个数。应用 client queue 可以大于 PgBouncer
server pool，但必须有长度、deadline 和拒绝策略；PgBouncer client connection 也不等于
PostgreSQL backend。

### 按事务语义分池

不能只按主机分池，还要按工作类型隔离：

| 池 | 特征 | 建议控制 |
|---|---|---|
| OLTP | 短事务、低尾延迟 | 最稳定的预算，快速失败 |
| batch/ETL | 长查询、吞吐优先 | 独立小池，可暂停 |
| admin/migration | 低频但高权限 | 保留直连/管理余量 |
| monitoring | 周期查询 | 有界并发，不能形成自激 |
| read-only | 可接受副本语义时 | 独立只读端点与 staleness 契约 |

若批处理与 OLTP 共用一池，批处理占满 server connections 后，所谓“主库健康”也无法
给在线请求提供 admission。Pigsty 的主写、只读、离线等服务端点可以提供路由分界，但
是否适合某事务仍由应用一致性语义决定。

### 事故时不要立刻放大 `max_connections`

扩大上限会让更多工作同时进入执行层，可能把一个有界连接拒绝变成内存、CPU、锁与
I/O 全面争用。只有在以下事实都成立时，调整才是经过评估的容量变更：

```text
current connections are useful, not retry duplicates
per-backend and per-query memory worst case is safe
CPU/I/O still have service headroom
new reserved/admin budget remains available
pool and application limits will not simply refill the new space
rollback and restart/reload semantics are known
```

在线事故的默认路线是收紧 admission，而不是把硬边界向后推。

## 34.2.2 重试放大、健康检查和短连接 {#item-34-2-2}

### 重试会把失败变成新流量

若原始到达率为 $\lambda_0$，每次失败平均触发 $r$ 次下一轮尝试，成功率没有及时恢复，
有效流量近似：

$$
\lambda_{\text{effective}}
= \lambda_0(1+r+r^2+\dots)
$$

当 $r\ge1$ 且没有 retry budget、deadline 或熔断时，系统进入正反馈：

```text
latency rises
  -> client timeout
  -> synchronized retry
  -> more connections and work
  -> latency rises again
```

日志里的“请求量上升”可能不是用户流量，而是同一批请求的重复尝试。必须用稳定的
request/idempotency key 区分 original、retry 与 hedge。

### 健康检查也会成为负载

设 $N$ 个应用实例，每个实例维护 $P$ 个 worker，每 $h$ 秒建立一次检查连接，则单健康
检查一项就可能产生约 $NP/h$ 次每秒建连。以下设计尤其危险：

- 每个业务请求先新建连接执行 `SELECT 1`；
- 每个 pod 同时启动并预热完整池；
- 多级代理各自以高频新连接探测；
- 故障时 autoscaling 新增实例，同时所有实例立刻重试；
- liveness 把短暂数据库慢判为应用死亡，形成重启风暴。

健康检查要区分：

```text
liveness: process itself是否需要重启
readiness: 是否接收新业务流量
dependency health: 数据库路径是否满足该业务语义
```

数据库慢通常应先让应用 not-ready 或熔断新请求，而不是把所有应用进程重启。

### 长连接也不是免疫

已有连接在代理切换、数据库重启、证书轮换或网络抖动后会同时重连。池应具备：

```text
randomized connection lifetime
startup/prewarm rate limit
connect timeout shorter than request deadline
bounded reconnect concurrency
exponential backoff with full jitter
global retry budget
```

不要给每一层各自配置十次重试。应用、驱动、service mesh、代理和任务框架叠加后，
最坏尝试次数是乘法。

## 34.2.3 限流、队列、连接预算与指数退避 {#item-34-2-3}

### 把过载变成显式 admission

好的过载控制不是“永不拒绝”，而是在系统仍能完成高价值工作时，尽早、明确地拒绝
超出预算的工作：

```text
admit if:
  class budget available
  AND request deadline still useful
  AND downstream breaker allows probe
else:
  reject/queue with bounded cost
```

队列必须同时有：

- 最大长度，防止内存成为下一瓶颈；
- 最大等待时间，过期工作不再进入数据库；
- 公平性或优先级，避免批处理饿死 OLTP；
- 可观测的 admitted/waited/rejected/expired 计数；
- drain 与 deploy 行为，避免发布时丢失或翻倍。

无限队列只是把快速失败变成更晚失败。Little's Law 给出稳定系统中的关系：

$$
L=\lambda W
$$

当平均等待 $W$ 上升时，在途数量 $L$ 也上升；如果请求 deadline 已经小于排队时间，
即使最终执行成功，对用户也没有价值。

### 指数退避要带随机抖动

一个常见策略：

```text
cap = min(max_backoff, base * 2^attempt)
sleep = random(0, cap)          # full jitter
stop when:
  request deadline exhausted
  retry budget exhausted
  operation is not idempotent/reconcilable
```

重试条件也要按错误分类：

| 错误 | 默认处理 |
|---|---|
| 认证/权限/语法 | 不重试，修配置或代码 |
| 连接拒绝/切换窗口 | 有预算、带 jitter 重试 |
| statement timeout | 先判是否仍在数据库执行及是否幂等 |
| deadlock/serialization failure | 整个事务按有限策略重试 |
| unknown COMMIT outcome | 先用业务 token 对账，不裸重放 |

### 连接事故的止血顺序

```text
1. freeze autoscaling/restart/retry amplification
2. preserve admin/reserved path
3. cap low-priority application admission
4. pause batch and migration pools
5. inspect active/waiting/root blockers
6. cancel exact low-value work if necessary
7. verify queue, success rate and tail latency
8. only after stability, repair capacity/config root cause
```

验收不是“连接数下降”，而是：

```text
new connection rejection stops or is intentional
pool waiting/expired work trends down
useful completion rate recovers
admin path remains available
no new memory/I/O bottleneck appears
temporary limits have an owner and expiry
```

---

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