22.2 连接的服务端成本
PostgreSQL 不是把所有客户端请求放进一个无状态线程池。经典架构中,每个已 认证客户端连接对应一个 backend process:
因此“连接数”同时占用身份、进程、内存、文件描述符、共享结构与调度能力。 连接池的目标不是让数据库接受无限请求,而是把大量客户端等待放到比 backend 更便宜、更可控的位置。
22.2.1 后端进程、内存、事务与会话状态
一个连接得到什么
backend 建立后持有:
- OS process、PID、栈和私有地址空间;
- 与 shared memory 的映射;
- database、role、application name、client address;
- session GUC 和 prepared statement;
- 临时 schema、临时表与 cursor;
LISTENregistration;- session-level advisory lock;
- relation/catalog cache;
- 当前事务、snapshot、lock 与 resource owner;
- socket buffer、日志上下文和统计状态。
可以观察:
state='idle' 只表示当前没有执行 query,不表示连接免费。它仍占用 backend
slot 和 process,并保留 session state。
connection、session、transaction、statement
四个边界不能混用:
直连 PostgreSQL 时:
事务池时:
应用仍觉得自己有一个长连接,但 server session 已不是它的私有状态容器。
私有内存与共享内存
连接成本不能简化成固定的“每连接 10 MB”。大致有:
work_mem 不是每个连接一次,而可能是每个执行节点、并行 worker 各自一次:
若 200 个 backend 同时执行多 hash/sort 查询,work_mem=64MB 并不意味着
最多使用 ;实际可能更高。
本章沙箱观察:
这些是配置事实,不是“500 个并发重查询安全”的证明。
共享结构也随连接上限变化
某些 shared memory 与数组在启动时按:
等参数估算。提高 max_connections 可能要求重启,并增加:
- backend/proc array;
- lock table 的潜在规模;
- per-backend shared bookkeeping;
- snapshot、predicate lock 等间接压力。
更隐蔽的是 CPU scheduler:大量 runnable backend 会争抢 CPU 和 cache, 吞吐可能下降而不是上升。
事务状态比连接数更危险
最常见的坏状态是:
它可能:
- 保留 snapshot,妨碍 vacuum 清理 dead tuple;
- 持有 row/table/advisory lock;
- 阻塞 DDL;
- 增长 xmin horizon;
- 长期占用事务池 backend。
观察:
护栏:
本章沙箱全局值是 600 秒。生产应按 workload 和 role 收紧,不要把长 ETL 与 OLTP 共用一个默认。
建连本身也有成本
一次新 PostgreSQL connection 包含:
若请求每次新建直连,建连成本和认证压力会被放大。连接池复用 server connection,既减少延迟,也把认证、TLS 和 backend churn 从每请求移开。
但复用不能隐藏身份边界:不同 database/user 通常形成不同 server pool, 不同 TLS、startup parameter 和 session feature 也可能阻止复用。
22.2.2 max_connections 不是容量规划答案
ceiling、budget 与 concurrency
区分三个数字:
把 ceiling 调高,只改变第一项。
先保留逃生通道
PostgreSQL 有:
达到普通上限后,拥有相应资格的角色仍可连接。PostgreSQL 18 中,
reserved_connections 与 pg_use_reserved_connections 可以为非 superuser
的受控运维角色保留槽。
预算示意:
但这仍不是建议同时运行 400 条重查询。PgBouncer server pool 应进一步把 active backend 控制在 CPU、I/O 和 workload 能承受的范围。
Little’s Law 先给量级
稳态系统中:
其中:
- :系统内平均并发请求;
- :每秒到达/完成请求数;
- :平均服务时间。
例如:
则平均 active database concurrency 约:
这不表示 pool 只设 20:要考虑 P95/P99、burst、锁等待、连接抖动和 headroom。 但它说明 500 backend 不一定比 40 更快。
排队不可消灭,只能选择位置
当 arrival rate 超过 service capacity:
总会有一个地方排队。好的设计让它:
- 边界明确;
- 有上限;
- 能超时;
- 可观察;
- 不占用昂贵 backend;
- 能按租户/服务隔离;
- 过载时先拒绝低价值工作。
坏的设计把 20,000 个客户端全部变成 PostgreSQL backend,再让 CPU scheduler 充当连接池。
max_connections 提高后的反效果
常见链条:
这是正反馈。正确诊断要看:
不能只看“连接占了 95%”。
何时真的需要提高上限
可能合理的场景:
- 大量长期 idle session,活跃并发低,且 session 语义无法池化;
- 多租户/多 database 需要更多独立最小 pool;
- 运维、复制、监控预算被明确分离;
- 经压测证明 backend 增长不破坏延迟/内存;
- 迁移到更大 CPU/内存并更新所有 guardrail。
变更前至少证明:
22.2.3 应用池、代理池与数据库预算
三层池的乘法
假设:
潜在客户端连接:
如果每个客户端直连 PostgreSQL,这也是潜在 backend 数。
经过 PgBouncer transaction pool 后,server connection 通常按:
形成。粗略上限:
还要受:
约束。
default_pool_size 是每个 pair,不是全局
本章 PgBouncer:
若有:
不能理解成“总共 50 条 PostgreSQL connection”。默认 pool size 会对每个 实际活跃 pair 生效,再被 db/user 上限裁剪。
因此 role 爆炸、database-per-tenant 和多 PgBouncer 实例会改变预算。
一个预算例子
业务:
潜在客户端:
设计 PgBouncer:
数据库预算:
数字必须由压测和生产 profile 校准,但计算结构应先存在。
应用 pool 不应等于线程数
每个 HTTP worker 都持一个数据库连接,常造成:
如果每次请求只有 5 ms 数据库时间,真实 active concurrency 可能很低。
应用 pool 应由:
- 每实例数据库并发;
- acquire deadline;
- request fan-out;
- transaction duration;
- burst headroom;
- PgBouncer queue 目标;
决定,而不是由 CPU thread 数机械复制。
多层 timeout 要共同设计
如果:
请求已取消后,数据库工作仍可能运行数十秒甚至无限。预算不是只有连接数, 还包括“连接占用多久”。
更合理的关系:
具体顺序会因重试和事务而变化,但下游不能普遍比上游 deadline 更长且不传播 取消。
按 workload 分池
不要让所有动作共享一个无差别 pair:
独立 role/pool 可以提供:
- 不同 pool size;
- 不同 statement/lock/idle timeout;
- 不同权限;
- 不同端点;
- 不同监控标签;
- 过载时独立降级。
但 role/pool 太多又会造成 pool multiplication。目标是按失败域与资源合同 分组,不是每个微服务随意造一个 50-connection pool。
预算表
| 消费者 | endpoint | client 上限 | server 上限 | active 目标 | queue timeout | 备注 |
|---|---|---|---|---|---|---|
| shop API | primary pooled | 128 | 32 | 24 | 200 ms | 短事务 |
| worker | primary pooled | 32 | 8 | 6 | 1 s | 可退避 |
| catalog read | replica pooled | 64 | 12 | 8 | 300 ms | 允许陈旧 |
| report | offline direct/pool | 8 | 4 | 2 | 2 s | 长查询限额 |
| migration | default direct | 2 | 2 | 1 | fail fast | 独立身份 |
| monitor/admin | direct/private | 10 | 10 | low | fail fast | 保留槽 |
client 上限 可以大于 server 上限,差值由有界队列吸收。若 arrival 持续
超过 capacity,队列必须超时/拒绝,不能无限累积。
观察预算是否成立
PostgreSQL:
PgBouncer:
重点列:
应用:
三个视角必须用同一 service/database/user/application_name 对齐。
本节检查表
参考资料
- PostgreSQL 18:Connections and Authentication
- PostgreSQL 18:Resource Consumption
- PostgreSQL 18:Monitoring Database Activity
- PgBouncer:Configuration
上一节:服务端点的语义 · 返回本章目录 · 下一节:PgBouncer 池化模式 · 查看全书目录 · 查看索引中心