跳至内容

22.5 连接预算与过载边界

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

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

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

22.5.1 按服务分配连接、并发与队列

先分服务,再分数字

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

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

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

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

按服务分配至少包括:

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

connection budget 不是 concurrency budget

transaction pool 可以有:

1000 client connections
40 server connections
20 typical active transactions

三者都合理,只要:

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

把所有实例相加

预算必须按全局 deployment:

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

例如:

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。可以用:

pg36_shop_app
pg36_shop_worker
pg36_shop_report
pg36_shop_migrate

建立不同预算与权限。

不要无界创建 role:

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 的接受条件

定义:

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 三处上限

本章渲染配置含:

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 拒绝过期工作。

一份分配账本

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 超时层级、取消传播与熔断

一次请求经过多个时钟

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

若各自独立设置,会出现:

上游 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 秒交互请求:

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 设置比一个全局值更实用:

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';

事务级可以:

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

lock_timeout

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

DDL/migration 常用:

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

意图是:

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

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

idle transaction timeout

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 保护的是下游与自身:

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 往往上升,正好放大最脆弱的下游。为服务定义:

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 的止血动作预留控制点

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

控制点一:客户端并发

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

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

控制点二:应用 pool

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

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

控制点三:PgBouncer

管理 console 可观察/控制:

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

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

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

控制点五:PostgreSQL role/database

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减少 arrivalqueue/DB active 降backlog 增分阶段恢复
缩 app pool限制 clientacquire wait 可控request reject恢复声明
pool PAUSE维护/切换server releasedclient waitRESUME
RECONNECT db刷新 backendrole/path probe 通过login burst无持久配置
HAProxy drain摘除 backendsessions 收敛容量下降enable/weight
cancel query释放资源query 消失事务 abort应用重试
terminate强制止血backend 结束outcome unknownreconcile

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

最小过载仪表盘

应用:

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

PgBouncer:

cl_active/cl_waiting
sv_active/sv_idle
maxwait
login/error

PostgreSQL:

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

代理:

backend status/backup use
current sessions/queue
connect errors

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

本节检查表

[ ] 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 可以复用这些控制点,而不是事故时发明

参考资料


上一节:路由与故障切换 · 返回本章目录 · 下一节:Pigsty 服务接入层 · 查看全书目录 · 查看索引中心

最后更新于