34.2 连接风暴与排队失控
PostgreSQL 采用一个 client connection 对应一个 backend process 的模型。连接不仅占一个 数字,还需要进程、内存、认证、catalog 初始化、socket、锁表与调度成本。连接池的 价值不是让数据库接受无限请求,而是把大量 client concurrency 变成有上限的 database concurrency。
34.2.1 数据库连接、代理池与应用池三层
三层都在排队
每层至少有四个量:
只看 PostgreSQL numbackends 会漏掉池前的队列;只看应用 pool size 又会漏掉多个
pod/进程/租户汇总后对数据库的总承诺。
一个粗略预算应满足:
其中 $A_i$ 是第 $i$ 类应用实例数,$P_i$ 是每实例可能占用的 server connection, $M$ 是迁移、运维和监控预算,$B$ 是故障切换/伸缩缓冲。对 PostgreSQL:
这里不是要求把所有层的上限设成同一个数。应用 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 全面争用。只有在以下事实都成立时,调整才是经过评估的容量变更:
在线事故的默认路线是收紧 admission,而不是把硬边界向后推。
34.2.2 重试放大、健康检查和短连接
重试会把失败变成新流量
若原始到达率为 $\lambda_0$,每次失败平均触发 $r$ 次下一轮尝试,成功率没有及时恢复, 有效流量近似:
当 $r\ge1$ 且没有 retry budget、deadline 或熔断时,系统进入正反馈:
日志里的“请求量上升”可能不是用户流量,而是同一批请求的重复尝试。必须用稳定的 request/idempotency key 区分 original、retry 与 hedge。
健康检查也会成为负载
设 $N$ 个应用实例,每个实例维护 $P$ 个 worker,每 $h$ 秒建立一次检查连接,则单健康 检查一项就可能产生约 $NP/h$ 次每秒建连。以下设计尤其危险:
- 每个业务请求先新建连接执行
SELECT 1; - 每个 pod 同时启动并预热完整池;
- 多级代理各自以高频新连接探测;
- 故障时 autoscaling 新增实例,同时所有实例立刻重试;
- liveness 把短暂数据库慢判为应用死亡,形成重启风暴。
健康检查要区分:
数据库慢通常应先让应用 not-ready 或熔断新请求,而不是把所有应用进程重启。
长连接也不是免疫
已有连接在代理切换、数据库重启、证书轮换或网络抖动后会同时重连。池应具备:
不要给每一层各自配置十次重试。应用、驱动、service mesh、代理和任务框架叠加后, 最坏尝试次数是乘法。
34.2.3 限流、队列、连接预算与指数退避
把过载变成显式 admission
好的过载控制不是“永不拒绝”,而是在系统仍能完成高价值工作时,尽早、明确地拒绝 超出预算的工作:
队列必须同时有:
- 最大长度,防止内存成为下一瓶颈;
- 最大等待时间,过期工作不再进入数据库;
- 公平性或优先级,避免批处理饿死 OLTP;
- 可观测的 admitted/waited/rejected/expired 计数;
- drain 与 deploy 行为,避免发布时丢失或翻倍。
无限队列只是把快速失败变成更晚失败。Little’s Law 给出稳定系统中的关系:
当平均等待 $W$ 上升时,在途数量 $L$ 也上升;如果请求 deadline 已经小于排队时间, 即使最终执行成功,对用户也没有价值。
指数退避要带随机抖动
一个常见策略:
重试条件也要按错误分类:
| 错误 | 默认处理 |
|---|---|
| 认证/权限/语法 | 不重试,修配置或代码 |
| 连接拒绝/切换窗口 | 有预算、带 jitter 重试 |
| statement timeout | 先判是否仍在数据库执行及是否幂等 |
| deadlock/serialization failure | 整个事务按有限策略重试 |
| unknown COMMIT outcome | 先用业务 token 对账,不裸重放 |
连接事故的止血顺序
验收不是“连接数下降”,而是:
上一节:第一动作:流量型还是保留型 · 返回本章目录 · 下一节:失控查询、锁与事务 · 查看全书目录 · 查看索引中心