bigywater.tech
Django · 43

CONN_MAX_AGE 与持久化连接

CONN_MAX_AGE 能省多少?代价是什么?这篇在 gunicorn + PostgreSQL 上分别量 0 / 60 / None 三种设置的延迟和连接数,再故意把数据库连接上限调到 30,看第二个服务起来之后会发生什么。

2.78 → 0.65 ms
300 个请求的中位延迟:CONN_MAX_AGE=0 → 60(本机回环)
300 → 1
300 个请求用到的数据库连接数
too many clients
持久连接总数超过 max_connections 时,新请求得到 500

延迟与连接数

实测 · 本机执行输出
  CONN_MAX_AGE       中位(ms)    p99(ms)     不同 pid 数       结束后连接数
  0(默认)                2.78       3.90          300            0
  60                   0.65       1.34            1            1
  None                 0.52       0.94            1            1

本机回环上,每请求建连的代价大约 2 ms(认证 + 后端进程 fork);持久连接把它省掉。真实网络、TLS、连接认证更复杂时,这个差距会更大(本文没有测)。

持久连接的代价

实测 · 本机执行输出
  服务 A(16 个处理线程)被打满后,数据库端连接数: 15  错误数: 0
  再开服务 B(同样 16 个线程)并打满:数据库端连接数 psql 自己也连不上:sorry, too many clients already  状态码/错误分布: {500: 3}
  数据库日志里的相关行: ['2026-09-30 20:19:54.979 CST [11263] FATAL:  sorry, too many clients already', '2026-09-30 20:19:55.045 CST [11265] FATAL:  sorry, too many clients already']

每个 worker × 线程都会各自占一个连接,并且在空闲时也占着。服务实例多了(扩容、滚动发布时新旧实例并存)很容易超过数据库的 max_connections。解决办法是限制线程/进程数,或者在应用和数据库之间放连接池(第 134 篇)。

分步演示:延迟与连接

CONN_MAX_AGE
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。gunicorn 26.2;PostgreSQL 16.15(本机临时实例,端口 9413,max_connections=30);延迟取 300 个请求的中位数与 p99
    • 没有验证:CONN_HEALTH_CHECKS、数据库端超时断开连接后的恢复、pgbouncer 等外部连接池、其它数据库(MySQL)的建连代价、跨机器网络与 TLS 的建连代价
    京ICP备2026067502号-1京公网安备11010502063487号