# 连接预算与过载边界

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

---

连接治理的目标不是让所有请求最终都能排到数据库，而是：

```text
正常负载低延迟通过
短 burst 在便宜位置有限排队
持续过载尽早拒绝或降级
高价值与控制流保留能力
取消与 deadline 向下传播
恢复时不产生重试洪峰
```

这需要同时预算 connection、active transaction、queue length、等待时间和
重试。只设置一个 `max_connections` 没有形成过载边界。

## 22.5.1 按服务分配连接、并发与队列 {#item-22-5-1}

### 先分服务，再分数字

一个数据库集群常同时承担：

```text
critical OLTP writes
interactive reads
background jobs
reporting/ETL
migration/admin
monitoring/backup/control
```

它们的价值、服务时间和失败策略不同。共享一个 pool 意味着：

```text
报表占满 backend
  -> 下单请求排队
      -> health/readiness 也超时
          -> orchestration 重启更多实例
              -> reconnect storm
```

按服务分配至少包括：

| 维度 | 问题 |
|---|---|
| client connections | 应用能保持多少逻辑连接 |
| server connections | 最多占多少 PostgreSQL backend |
| active concurrency | 同时执行多少事务/查询 |
| queue length/time | 多久后拒绝，最多积压多少 |
| resource | CPU、I/O、work_mem、temp、lock |
| priority | 过载时谁先被削减 |
| reserve | 谁能在拥塞时进入控制面 |

### connection budget 不是 concurrency budget

transaction pool 可以有：

```text
1000 client connections
40 server connections
20 typical active transactions
```

三者都合理，只要：

- client process/FD/TLS 成本可承受；
- server pool 不超过数据库预算；
- active workload 经压测；
- 960 个等待者不会无限停留；
- deadline 和拒绝策略有效。

### 把所有实例相加

预算必须按全局 deployment：

```text
blue version
green version
autoscaling maximum
cron workers
manual jobs
disaster-recovery warm instance
```

例如：

```text
normal 20 pods × pool 8     = 160 clients
deploy overlap another 20   = 160
autoscale headroom 10       =  80
workers 8 × pool 4          =  32
total potential             = 432
```

若 PgBouncer server pool 是 32，客户端能排队；若直连，则一次 blue/green
发布就可能把 backend 翻倍。

### database/user pair 的隔离

PgBouncer pool key 通常包含 database 与 user。可以用：

```text
pg36_shop_app
pg36_shop_worker
pg36_shop_report
pg36_shop_migrate
```

建立不同预算与权限。

不要无界创建 role：

```text
100 tenants × default_pool_size 20
```

即使每个 pair 很小，乘法也可能超过 PostgreSQL。可通过：

- `max_db_connections`；
- `max_user_connections`；
- per-database/user override；
- group role + application context；
- tenant 分片；
- idle pool cleanup；

控制。

### 负载并发门槛

连接池限制的是 backend 数，不直接限制一条连接里 query 的资源。还需要：

- application concurrency semaphore；
- job worker count；
- query governor；
- statement timeout；
- role/database resource policy；
- workload isolation/offline replica。

例如 8 条并行 hash join 可能比 32 条短索引查询更重。server pool size 要按
workload mix 压测。

### queue 的接受条件

定义：

```text
Q_max      最大等待者
W_max      最大等待时间
λ          到达率
μ          每 server slot 服务率
c          server slots
```

长期稳定至少要求：

\[
\lambda < c\mu
\]

否则任何有限 queue 最终都会满。

短 burst 的近似吸收能力：

\[
Q_{\text{needed}}
\approx
\int_{\text{burst}}
\max(0,\lambda(t)-c\mu(t))dt
\]

这不是精确 queueing model，但迫使团队问“burst 多大、持续多久”，而不是把
max queue 随手设成 10,000。

### Pigsty/HAProxy/PgBouncer 三处上限

本章渲染配置含：

```text
HAProxy service maxconn          5000
HAProxy per backend maxconn      3000
HAProxy per backend maxqueue      128
PgBouncer max_client_conn       20000
PgBouncer default_pool_size        50
PgBouncer reserve_pool_size        30
PgBouncer max_db_connections      100
PgBouncer max_user_connections    100
PostgreSQL max_connections         500
```

大数字不表示应使用到它。真正的有效边界是这些限制、活跃 pair 和所有实例
总和的组合。

如果 HAProxy 允许 5000、PgBouncer 允许 20000，而应用 deadline 2 秒，
仍应在更早层用 application concurrency/query wait timeout 拒绝过期工作。

### 一份分配账本

```yaml
cluster_backend_ceiling: 500
reserved:
  superuser: 10
  platform_admin_monitor: 40
  incident_control: 20
  maintenance_replication: 30
allocatable_workload: 400
services:
  shop_rw:
    server_pool: 40
    reserve: 8
    active_target: 24
    queue_timeout: 200ms
  shop_ro:
    server_pool_per_replica: 20
    active_target: 12
    queue_timeout: 300ms
  report:
    server_pool: 4
    active_target: 2
    statement_timeout: 10min
  migration:
    direct_connections: 2
headroom:
  unallocated: 100
```

headroom 不是浪费，而是吸收估算误差、maintenance 和 incident action。

## 22.5.2 超时层级、取消传播与熔断 {#item-22-5-2}

### 一次请求经过多个时钟

```text
user deadline
HTTP/RPC deadline
application pool acquire timeout
DNS/connect/TLS timeout
HAProxy queue/connect timeout
PgBouncer query_wait_timeout
PostgreSQL lock_timeout
PostgreSQL statement_timeout
idle_in_transaction_session_timeout
client read/write socket timeout
```

若各自独立设置，会出现：

```text
上游 2 秒取消
下游 pool 还等 120 秒
SQL 继续跑 10 分钟
失败后 middleware 再重试
```

资源在用户离开后继续消耗。

### deadline budget

把请求总预算 \(D\) 分解：

\[
D =
T_{\text{acquire}}
+T_{\text{connect}}
+T_{\text{queue}}
+T_{\text{lock}}
+T_{\text{execute}}
+T_{\text{return}}
+T_{\text{margin}}
\]

各层不一定串行，有些重叠；这个式子是设计账本，不是精确 profiler。

例如 2 秒交互请求：

```text
app acquire             150 ms
connect/role check      300 ms
database statement     1200 ms
return/margin           350 ms
```

PgBouncer `query_wait_timeout=120s` 显然不匹配该请求。可以按服务 override，
或让应用 acquire/overall deadline 更早终止并正确 cancel。

### connect timeout 不是 failover timeout

多 host 串行尝试时：

\[
T_{\text{connect worst}}
\approx
\sum_{\text{host}} T_{\text{connect host}}
+ DNS/TLS/backoff
\]

三个 host × 5 秒可能已经超过 request deadline。驱动是否并行尝试、每 host
还是全局 timeout，要按版本确认。

### `statement_timeout`

PostgreSQL 从收到命令开始计时；达到后取消当前 statement。它不一定包含：

- 应用 pool 等待；
- PgBouncer queue；
- DNS/TCP/TLS；
- 客户端处理结果；
- 上一次 idle transaction。

按 role/database 设置比一个全局值更实用：

```sql
ALTER ROLE pg36_shop_app IN DATABASE pg36_shop
  SET statement_timeout = '3s';

ALTER ROLE pg36_shop_report IN DATABASE pg36_shop
  SET statement_timeout = '10min';
```

事务级可以：

```sql
BEGIN;
SET LOCAL statement_timeout = '800ms';
...
COMMIT;
```

### `lock_timeout`

它只限制等待 lock 的时间，不限制 query 总执行时间。

DDL/migration 常用：

```sql
SET lock_timeout = '1s';
SET statement_timeout = '30min';
```

意图是：

```text
拿不到锁快速失败，不在生产长队列中等待；
拿到锁后允许受控操作运行。
```

不要把 lock timeout 设置得比 statement timeout 更长而期待它生效。

### idle transaction timeout

```sql
idle_in_transaction_session_timeout
```

切断已开始事务但长期不发 query 的 session。它是 vacuum/lock 保护，不应拿来
清理普通 idle pool connection。

还有普通 `idle_session_timeout`，但连接池可能把 idle connection 视为资产；
使用前要评估 reconnect churn 和 middleware 兼容。

### 取消传播

客户端取消 PostgreSQL query 通常需要单独 cancel request/connection path。
经过 pool/proxy 时要验证：

- cancel 能定位正确 backend；
- client 已换 backend 后不会 cancel 别人；
- proxy 是否转发；
- timeout 后 transaction 是否处于 aborted；
- connection 是否应丢弃；
- server query 是否确实停止。

不能只看到 HTTP 499/timeout 就认为数据库工作结束。

### 熔断器

breaker 保护的是下游与自身：

```text
closed     正常请求
open       快速拒绝，停止放大故障
half-open  少量探针判断恢复
```

触发信号应比“任意 SQL error”精细：

- pool acquire timeout；
- connection/role check failure；
- sustained queue；
- downstream saturation；
- known infrastructure outage。

不要因业务约束错误、syntax error 或唯一冲突打开数据库 breaker。

### retry budget

如果原始请求率为 \(\lambda\)，平均重试 \(r\) 次：

\[
\lambda_{\text{downstream}}
= \lambda(1+r)
\]

故障时 `r` 往往上升，正好放大最脆弱的下游。为服务定义：

```text
maximum retries per request
maximum retry traffic percentage
backoff+jitter
non-retryable SQLSTATE
outcome-unknown reconciliation
```

### load shedding

在数据库彻底饱和前：

1. 拒绝可选报表/推荐；
2. 降低 background worker；
3. 使用缓存/较旧副本；
4. 限制 expensive endpoint；
5. 保留写入/控制面；
6. 必要时进入只读或功能降级。

load shedding 是业务决策，不应完全交给随机 connection timeout。

## 22.5.3 为 ch34 的止血动作预留控制点 {#item-22-5-3}

第 34 章会处理连接风暴、CPU、内存、磁盘和 I/O 过载。本章要提前提供可用
控制点，否则事故中只能粗暴重启。

### 控制点一：客户端并发

```text
feature flag
rate limiter
per-tenant quota
worker concurrency
queue consumer pause
autoscaler maximum
retry switch
```

优点：最接近业务价值，能在请求进入数据库前止血。

### 控制点二：应用 pool

```text
max size
min idle
acquire timeout
connection lifetime
idle timeout
warmup rate
validation
```

动态缩 pool 可能只影响新借用，不能立即终止 in-flight transaction。变更行为
按具体 driver 验证。

### 控制点三：PgBouncer

管理 console 可观察/控制：

```text
SHOW POOLS / STATS / CLIENTS / SERVERS
PAUSE / RESUME
DISABLE / ENABLE
RECONNECT
RELOAD
SET changeable_setting
KILL database
```

这些动作风险不同：

- `PAUSE` 等待 server connection 释放，可用于维护；
- `RECONNECT database` 刷新 server connection；
- `KILL` 更具破坏性；
- runtime `SET` 会形成声明漂移；
- global action blast radius 可能跨服务。

必须有 exact database/instance/role guard 和复位证据。

### 控制点四：HAProxy

```text
disable/enable server
backend weight
maxconn/maxqueue
drain
health threshold
service removal
```

把流量移到副本/其他主库前，先确认目标容量与一致性。转移过载往往只是移动
事故。

### 控制点五：PostgreSQL role/database

```sql
ALTER ROLE ... CONNECTION LIMIT ...;
ALTER DATABASE ... ALLOW_CONNECTIONS false;
ALTER ROLE ... SET statement_timeout ...;
ALTER ROLE ... SET work_mem ...;
REVOKE CONNECT ...;
SELECT pg_cancel_backend(...);
SELECT pg_terminate_backend(...);
```

其中 revoke/terminate 可能影响业务或锁，属于受控事故动作。不要把示例 SQL
做成无 guard 的一键脚本。

### 控制点六：保留管理路径

过载时最怕 DBA 也连不进去。预留：

- superuser/reserved connection；
- 独立 admin role；
- direct endpoint；
- source network allowlist；
- 小而独立的 admin pool；
- break-glass credential；
- out-of-band host access。

监控 exporter 也不应完全依赖已满的普通业务池。

### 每个控制点都要有回滚

事故动作表：

| 动作 | 目的 | 成功证据 | 副作用 | 复位 |
|---|---|---|---|---|
| 降 worker | 减少 arrival | queue/DB active 降 | backlog 增 | 分阶段恢复 |
| 缩 app pool | 限制 client | acquire wait 可控 | request reject | 恢复声明 |
| pool PAUSE | 维护/切换 | server released | client wait | RESUME |
| RECONNECT db | 刷新 backend | role/path probe 通过 | login burst | 无持久配置 |
| HAProxy drain | 摘除 backend | sessions 收敛 | 容量下降 | enable/weight |
| cancel query | 释放资源 | query 消失 | 事务 abort | 应用重试 |
| terminate | 强制止血 | backend 结束 | outcome unknown | reconcile |

“止血成功”不等于“事故解决”。必须保留证据、找根因和复位。

### 最小过载仪表盘

应用：

```text
request rate/error/latency
pool waiters/acquire timeout
retry rate
in-flight by endpoint
```

PgBouncer：

```text
cl_active/cl_waiting
sv_active/sv_idle
maxwait
login/error
```

PostgreSQL：

```text
active/idle in transaction
wait events/locks
CPU/run queue
I/O latency
temp bytes
WAL/replication lag
```

代理：

```text
backend status/backup use
current sessions/queue
connect errors
```

端到端 correlation 必须有 service、database、user、application_name 和时间。

## 本节检查表

```text
[ ] service 按价值、服务时间和失败策略分预算
[ ] client/server/active/queue 四种上限分开
[ ] blue-green/autoscale/worker 乘法已计入
[ ] sustained overload 有拒绝而非无限排队
[ ] timeout 形成一份 deadline budget
[ ] cancel 到 PostgreSQL backend 已实测
[ ] retry 有 SQLSTATE 分类、budget 和 jitter
[ ] optional workload 能独立 shed
[ ] admin/monitor 有保留连接与直连路径
[ ] PgBouncer/HAProxy/PostgreSQL 控制动作有 guard 与复位
[ ] ch34 可以复用这些控制点，而不是事故时发明
```

## 参考资料

- [PostgreSQL 18：Client Connection Defaults](https://www.postgresql.org/docs/18/runtime-config-client.html)
- [PostgreSQL 18：Server Configuration](https://www.postgresql.org/docs/18/runtime-config.html)
- [PgBouncer：Configuration](https://www.pgbouncer.org/config.html)
- [PgBouncer：Administration console](https://www.pgbouncer.org/usage.html)

---

[上一节：路由与故障切换](../04/) · [返回本章目录](../) · [下一节：Pigsty 服务接入层](../06/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
