跳到主要内容

数据库连接池:AI 流水线中被忽视的性能瓶颈

· 阅读需 10 分钟
Tian Pan
Software Engineer

你的 AI 功能上线了。在预发环境中,响应时间看起来还不错。一周后,生产环境开始出现神秘的 p99 尖峰——在中等负载下,延迟从 800ms 飙升至 8 秒,而 GPU 压力正常,模型没有报错,也找不到明显原因。你扩容了更多副本,没有改善。你对模型服务做了性能剖析,没有问题。你加了缓存,还是没用。

最终,有人查了数据库连接池的等待时间。从第三天起,它的利用率就已经高达 95%。

这是 AI 生产事故中最常见的一类,却鲜有人谈及——因为连接池耗尽的表现很像模型变慢。症状出现在错误的层级:你看到的是 LLM 调用延迟高,而不是数据库查询慢,所以定位问题往往需要数天,而用户一直在忍受降级的响应。

根本原因在于:AI 工作负载打破了传统连接池容量规划所有的内在假设。为 OLTP Web 流量设计的连接池,在加入 LLM 生成、检索和 Agent 状态持久化之后就会以难以监测、难以预判的方式崩溃。

为什么标准的容量规划公式不适用

数据库连接池容量规划的经典公式来自对高吞吐 OLTP 系统的研究。PostgreSQL 官方文档建议从以下公式出发:

connections = (core_count × 2) + effective_spindle_count

对于配备 SSD 的 4 核机器,约为 8–9 个连接。HikariCP 的文档更进一步,引用 Oracle 的基准测试表明:当团队将连接池从数百个缩减至几十个时,吞吐量提升了 50 倍。这个结论违反直觉:连接越多意味着更多的上下文切换、更激烈的锁竞争,以及更低的吞吐量。

这套数学假设了一种特定的工作负载模型——短事务、高并发、快速周转。典型的 OLTP Web 请求持有数据库连接的时间为 5–20 毫秒。以 10ms 的平均值计算,20 个连接的连接池每秒可服务 2000 个请求,才会出现排队。

LLM token 生成需要 2–60 秒,取决于输出长度。

用同样的公式套 AI 流水线:假设每个用户请求触发一次生成,生成期间持有连接 15 秒,那么 20 个连接的连接池每秒只能服务 1.3 个请求。在这个速率下,10 个并发用户的突发流量不到 10 秒就会把连接池打满,之后的请求全部排队。

连接池从来不是你关注的那个瓶颈。但一旦你在应用层接入了 LLM,它就会立刻成为瓶颈。

AI 工作负载的三种典型故障模式

模式一:检索突发

RAG 流水线对数据库的冲击方式与 API 驱动的功能截然不同。用户提交查询后,检索步骤会扇出——获取向量嵌入、查找元数据、与用户上下文做 join、按权限过滤。这可能意味着在 100ms 窗口内产生 5–15 个并行数据库调用。

在传统 API 场景中,这些会是分散在用户会话中的串行查询。在 RAG 流水线中,它们是并发的,并且同时打到连接池上。

为串行访问设计的连接池(比如 10 个连接)会被单次复杂检索打满。两个并发用户触发 20–30 个同时查询。到第三个用户,连接开始排队;到第十个用户,超时开始出现。

故障表现为检索缓慢,但实际原因是连接等待时间。向量相似性搜索 15ms 就能返回,但应用程序要等 400ms 才能拿到连接,查询甚至还没开始。

模式二:生成期间持有连接

这是代价最高的故障模式。场景很常见:应用打开一个数据库事务记录用户请求,然后调用 LLM API 生成回复,最后把结果写回事务再提交。

在传统系统中,在外部 HTTP 调用期间持有事务已经很危险。在 AI 流水线中,这个外部 HTTP 调用需要 10–30 秒,还依赖于一个延迟本身就不稳定的 GPU 服务。

在整个生成期间,连接处于空闲状态——不做任何工作,不执行任何查询,只是在模型"思考"时占着一个事务槽。与此同时,新请求不断涌入并排队等待。

曾因生成期间丢失写入(模型返回了结果,但应用崩溃没来得及持久化)而吃过亏的团队,常常把长事务作为安全保障。出发点是对的,实现方式是错的。正确做法不是一个长事务,而是两个短事务:立即持久化请求后释放连接,做生成,然后开一个新连接持久化结果。

模式三:Agent 写入风暴

多步骤 Agent 产生的写入模式是标准连接池处理起来最吃力的:大量小的、突发性的、并行的状态持久化写入。

单次 Agent 执行可能会在每次工具调用后写入检查点状态、在推理步骤之间更新工作记忆、记录工具调用日志以供可观测性使用,以及在完成时写入最终输出。在一个 20 个 Agent 并发运行、每次执行产生 5–10 次写入的系统中,短时间内会有 100–200 次小写入,中间夹杂着空闲间隙。

为稳态吞吐量设计的连接池在空闲间隙时有富余容量,随后被超过池容量的请求峰值打爆。结果是随机出现的排队等待尖峰,与任何被监控的指标都没有相关性——因为这个模式只在 Agent 层级的并发中才会涌现,在查询层级看不到。

异步取消陷阱

还有第四种故障模式,专属于异步 Python 技术栈,比其他模式更隐蔽,因为它导致的是连接泄漏而非竞争。

当用户取消一个长时间运行的生成——按下停止键、关闭浏览器标签页或触发请求超时——asyncio 任务会被取消。如果该任务持有一个数据库会话,并且取消发生在 await 链的错误位置,连接可能无法被归还到连接池。

这是 SQLAlchemy 异步引擎和 asyncpg 中真实存在的、有据可查的问题。在连接签出或事务执行期间发生任务取消,可能导致连接处于不一致状态——既不被归还到连接池,也不被正确关闭。在负载下,每次取消操作都会泄漏一个连接。在一个有 5% 用户取消长时间运行请求的系统中,连接池每分钟缩减 5%,直至耗尽。

修复方式是在数据库会话外层包装一个显式的清理上下文来处理取消:

async def generate_with_cleanup(session_factory, user_request):
async with session_factory() as session:
try:
await session.execute(log_request(user_request))
await session.commit()
finally:
await session.close() # 显式关闭,在取消时也能生效

另一个选项是在不应被中断的连接操作上使用 asyncio.shield(),但这更难推理,可能会屏蔽掉合法的取消。显式管理会话生命周期并使用 try/finally 的方式更具防御性。

为 AI 工作负载规划连接池容量

面向 AI 流水线的连接池容量规划,正确的思维模型从 Little 定律出发:

concurrent_connections = arrival_rate × average_hold_time

对于每秒 10 个请求、每次请求平均持有连接 200ms(考虑检索扇出)的 RAG 流水线,平均需要 2 个并发连接——但分布至关重要。20 个并发请求每个需要 5 个并行查询,峰值时达到 100 个并发连接。

分开估算每个组件:

  • 检索:估算峰值并发用户数 × 每次检索扇出的查询数 × 检索延迟
  • 生成期间持有:尽可能消除;如果无法消除,按峰值并发活跃生成数规划
  • Agent 写入:估算并发 Agent 数 × 每次 Agent 执行的写入数 × 写入延迟

然后按峰值而非平均值来规划连接池容量。与 OLTP 系统不同,Little 定律在稳态流量下可以很好地取均值,而 AI 工作负载会出现尖峰。连接池应为突发流量设计,配合合适的溢出限制,并将队列超时设置得足够激进,让慢速降级能快速失败,而非悄悄级联。

面向 100 个并发用户的生产 RAG 系统,一个实用的起点是:读池(检索查询)和写池(日志、状态持久化)分开,读池容量设置为 Little 定律均值预测的 3–5 倍,同时设置激进的超时(连接等待最长 500ms),以便快速暴露耗尽问题,而不是让延迟悄悄增长。

监控什么

标准的数据库监控完全看不到 AI 特有的故障模式。查询延迟看起来正常,因为单次查询很快。连接数看起来正常,因为连接没有明显泄漏。能暴露问题的指标是连接池等待时间——请求在查询开始之前,等待连接所花的时间。

在你的 AI 流水线可观测性中加入以下指标:

  • 按请求类型(检索 vs. 写入 vs. 生成)统计的连接池等待时间:可以识别是哪个流水线阶段造成了竞争
  • p99 连接池利用率(而非均值):平均利用率看起来健康时,尾部利用率可能已经饱和
  • 活跃连接持续时长直方图:如果你看到连接被持有超过 15 秒,说明有人在生成期间持有连接
  • 请求取消率 vs. 连接池大小趋势:泄漏信号,在标准连接池大小指标中不会显现

将连接池等待时间超过 50ms 视为事故,而非警告。一旦 p99 等待时间达到 500ms,你已经在为相当大比例的用户降级服务了。

设计原则

底层原则是:数据库连接应在完成数据库操作所需的最短时间内持有——不是在用户请求的整个生命周期内,更不是在 LLM 生成期间。

AI 系统倾向于将"请求生命周期"建模为一个单元:接收查询、检索上下文、生成回复、持久化结果。朴素实现会将其映射到一个跨越整个生命周期的单一数据库会话。这种设计在开发环境下可以跑通,在生产环境下会崩溃。

正确的设计将数据库访问视为发生在流水线起点和终点的离散操作,生成本身在任何连接上下文之外执行。检索打开连接、获取数据、关闭连接,将结果返回到内存状态。生成在内存状态上运行,不持有任何连接。持久化打开新连接、写入结果、关闭连接。三个短暂的连接窗口,而非一个长连接。

这种设计实现起来更复杂,但它是唯一能规模化的方案。为 OLTP 工作负载构建的连接池假定数据库操作快速、离散。AI 流水线需要被构建成尊重这个假设的形态——而不是对抗它。


能在规模上可靠交付 AI 系统的团队,不是那些 GPU 容量最多或模型延迟最低的团队。而是那些把每一层基础设施都视为一等性能关注点的团队——包括那些十年前就被认为"已经解决了"的层。

References:Let's stay in touch and Follow me for more thoughts and updates