Agent 的数据库连接池管理:高并发场景下 Agent 怎么复用数据库连接

早上 10 点,你的 Agent 服务突然炸了。日志里全是 connection reset,数据库端 max_connections 被占满,每一根连接上还挂着一个正在“思考”的 Agent——它们正在等大模型返回结果,30 秒过去了,还没想好下一步该查哪张表。

AI technology illustration

这不是段子。我见过不止一次。很多人把 Agent 当成普通 Web 服务来调数据库,并发一上来,第一个去世的往往是数据库连接池。

Agent 和普通请求的数据库访问,差在哪

传统 Web 请求长这样:进来一个请求,从连接池拿一个连接,执行几条 SQL,响应返回,连接归还。连接被占用的时间约等于 SQL 执行时间——几十毫秒到几百毫秒。

Agent 任务完全不一样。一次 Agent 运行 = 多轮“思考(调 LLM) + 行动(调工具)”。行动里可能包含数据库查询,但思考的时间可能是查询的上百倍。麻烦的是,如果你在工具链路里开启了事务,或者把连接挂在 Agent 会话对象上,连接就会在 LLM 思考期间被空占着。数据库不知道你在思考,只知道这根连接是活跃的。

维度 传统请求 Agent 任务
连接占用时间 几十毫秒 几百毫秒到几分钟
占用是否等于 SQL 执行时间 基本相等 通常远大于 SQL 执行时间
典型并发 成百上千 几十个就够呛
失败模式 等待超时、队列堆积 连接池耗尽、数据库连接堆积

看到区别了吗?Agent 并发数不高,但每个连接被“睡”住的时间太长,池子很快就被堵死。

连接池复用的底层假设

连接池存在的理由是“创建连接很贵”。HikariCP 的 官方 wiki 里给过一个估算公式:最大连接数 ≈ CPU 核数 × 2 + 磁盘数。理由是并行 I/O 有上限,连接再多也只是让数据库忙于上下文切换。

创建连接到底有多贵?一次 TCP 握手加认证,本机也要几毫秒,跨机房轻松上百毫秒。所以连接池本身是必要的。但池化解决的是“反复创建”的问题,解决不了“一根连接被毫无意义地捏着不放”的问题。Agent 场景里,后者才是大头。

但这个公式有一个隐含假设:每个连接都在“干活”。如果你池子里有一半连接在等 LLM 响应,这个公式就是废纸。我早期就吃过这个亏——把 maxPoolSize 从 10 调到 50,数据库 CPU 没升多少,连接却被 Agent 全占着,旁边普通 API 反而拿不到连接。

我踩过的坑:连接陪 Agent 一起“思考”

以 LangChain 的 SQLDatabase Toolkit 为例,内部用 SQLAlchemy 管理引擎和连接池。正常情况下,每次工具调用从池里借连接,跑完 SQL 立刻归还。这很好。

但很多二开代码不是这么干的。为了在多次工具调用之间保留临时表或事务上下文,开发者把 connection 对象塞进 Agent 的 state,任务不结束,连接不归还。你说复用了吗?复用了。但连接从“干活”变成了“占坑”。高并发下,这比不用连接池还可怕。

我掉过更深的坑:某个工具函数查完数据忘了关连接,每个 Agent 会话泄漏一根。跑了一小时后,数据库连接数冲破上限。后来打开连接池的泄漏检测,发现每次任务的连接持有时长曲线和 LLM 调用时长几乎完全重合——连接是在陪着 AI 思考呢。

这件事让我想通了一个关键点:Agent 场景的连接管理,核心矛盾不是“创建连接贵”,而是“持有连接但不干活的时间太贵”。

高并发下真正有效的五件事

  • 让工具保持无状态。 每次工具调用都从池里借连接,执行完立刻归还。跨调用的临时状态用临时表或表变量存,不要用连接存。
  • 把事务边界压缩到最短。 如果某个操作需要事务,确保事务内的所有 SQL 都执行完,再交给 LLM 去思考。绝对不要让一个事务开着等 LLM 返回——这是最典型的长占坑。
  • 在数据库前面加事务级连接池代理。 比如 PostgreSQL 配 PgBouncer 的 transaction 模式。Agent 端可以维持自己的客户端连接,但 PgBouncer 只在事务真正执行时占用数据库连接,事务一结束就回收给其他任务。这样 Agent 的“思考时间”不会空占数据库连接。
  • 限制 Agent 并发数。 用信号量控制同时运行的 Agent 数量,宁可让任务排队,也别让数据库陪葬。这不是怂,这是背压。
  • 给 Agent 单独建一个连接池。 别让 Agent 服务和普通 API 共享同一个池。Agent 的连接占用模式和普通请求完全不同,分开后至少能隔离故障,不会一处爆炸全站瘫痪。

生产级配置长什么样

# HikariCP 针对 Agent 服务的推荐参数
maximumPoolSize=20
minimumIdle=5
connectionTimeout=3000
idleTimeout=600000
maxLifetime=1800000
leakDetectionThreshold=10000

这里 leakDetectionThreshold 是重点。如果连接持有超过 10 秒,日志就会告警。Agent 的 SQL 本身通常只需几百毫秒,超过 10 秒大概率是连接被拿去陪 AI 思考了。

# PgBouncer transaction 模式
[databases]
mydb = host=127.0.0.1 port=5432 pool_size=20

[pgbouncer]
pool_mode = transaction
max_client_conn = 2000
default_pool_size = 20

注意 pool_mode = transaction 是核心。客户端连接数可以开到很大,但真实数据库连接受 default_pool_size 限制。Agent 端随便挂,数据库端稳如老狗。

如果你的 Agent 跑在 Kubernetes 上,记住一点:每个 Pod 都有自己的连接池,数据库看到的连接总数是 Pod 数 × 池大小。这时候更要克制,别把数据库的 max_connections 当“大家分着用”的资源——每个 Pod 都会默认把自己填满。

三个最容易踩的误解

“连接池越大越好。” 错。PostgreSQL 的 Wiki 很早就说过,连接数超过 CPU 核数到一定倍数后,性能不但不升,反而下降。每个连接都要维护私有缓存和锁,太多连接会增加调度开销。Agent 场景下,加大池子往往只是掩盖连接泄漏,拖延问题。

“Agent 用连接池和普通服务没区别。” 区别大了。普通服务是“瞬时请求多”,Agent 是“长占用任务多”。前者拼池子吞吐,后者拼连接周转速度。你按普通服务的思路调参,就会在 Agent 上线当天被教育。

“事务级连接池代理会影响功能。” 看情况。PgBouncer 的 transaction 模式不支持跨事务的会话级特性,比如临时表、LISTEN/NOTIFY。但绝大多数 Agent 的数据库操作根本用不到这些。如果你真的需要跨步骤会话状态,应该优先重构业务逻辑,而不是让代理模式四不像。

最后

所以,当有人告诉我“Agent 高并发连不上数据库”,我的第一反应永远不是调大连接池,而是去查工具代码里连接的生命周期跟着谁走。跟着请求走,稳;跟着 Agent 的思考走,必炸。

在把 Agent 推上生产之前,我建议你先做两件事:一是给连接池装上占用时长监控,二是把所有工具函数过一遍,确保连接即借即还。然后再去谈 PgBouncer、读写分离和容量规划。连接复用不是目的,让连接持续干活才是。这大概就是 Agent 场景下数据库连接池管理的全部秘密。

原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/643.html

(0)
上一篇 1天前
下一篇 1天前

相关推荐