RAG 服务的检索链路在低并发时表现正常,一旦查询 QPS 上来,单次检索耗时从几十毫秒恶化到数百毫秒甚至触发超时。现象看起来像向量库变慢了,但真实瓶颈未必在向量库。高并发下的延迟劣化通常是多个环节同时恶化,排查的核心不是直接调参,而是先把耗时拆开,确认它到底花在哪一层。

第一步:把耗时拆到具体段

一条完整的 RAG 检索链路至少包含:query 组装与向量化、向量检索、候选结果重排、上下文拼装。延迟突增时,先确认向量化与重排这两段的耗时是否变化——它们同样是高并发下会劣化的环节。

确认这两段正常后,再把向量检索的端到端耗时继续拆分:

  1. 客户端从连接池获取连接;
  2. 请求从客户端到服务端的网络往返;
  3. 服务端排队等待处理;
  4. 服务端实际执行索引查询。

做法是在调用向量库的代码前后打链路追踪 span,分别记录“开始调用”“拿到连接”“发出请求”“收到响应”四个时间点,对应上面四段。没有完整 tracing 时,用日志记录同样的时间戳差也能定位。

两个关键判断:

  • 如果“拿到连接”到“发出请求”的等待时间变长,而服务端处理时间没变,问题在连接池或服务端排队;
  • 如果“发出请求”到“收到响应”的服务端耗时同步变长,才需要继续查索引参数和资源瓶颈。

客户端超时与重试:慢查询如何变成雪崩

高并发下延迟升高后,最先被触发的是客户端超时。三个检查点:

  • 超时阈值是否过短。正常 p99 已经很接近阈值时,一次抖动就能让大量查询被误判为失败;
  • 重试次数与重试策略。超时后立即重试会让请求量翻倍,形成“超时 → 重试 → 服务端更忙 → 更多超时”的雪崩;
  • 重试是否有退避。无退避的固定间隔重试,在高并发下会形成同步冲击。

一个可行的做法:把超时阈值放宽到明显高于正常 p99 的水平,将单请求最大重试次数限制在 1 次,并加随机退避。压测时对比“客户端发起的查询量”与“服务端实际收到的查询量”,两者不一致就说明重试放大确实存在。

连接池:高并发下最先耗尽的资源

大多数向量库客户端通过连接池复用连接。连接池耗尽时,新查询不会直接失败,而是阻塞等待空闲连接,表现为“总耗时变长,但服务端单次查询耗时不变”。

需要查看的指标:连接池最大连接数、活跃连接数是否长期接近上限、获取连接的平均等待时间、空闲连接的回收策略是否导致频繁重建。连接建立本身要付出 TCP 握手和可能的认证开销,高并发下频繁建连会额外消耗 CPU 与端口资源。

从工程角度看,连接池大小要与后端实例的并发处理能力匹配,而不是无脑调大。连接数超过服务端查询并发上限后,多余连接只增加排队和上下文切换。压测时可以逐步调大连接池:如果延迟没有下降甚至上升,说明瓶颈已经不在连接池。

服务端:查询队列与处理能力

向量数据库服务端在高并发下通常有并发查询上限,超出部分的请求进入排队。这解释了延迟“突增”而不是缓慢上升:查询到达率一旦越过服务端处理能力,排队长度会迅速累积,端到端延迟随之线性恶化。

排查方式:

  • 查看服务端是否暴露当前排队数、拒绝数或限流指标;
  • 对比服务端处理耗时与端到端耗时。如果端到端远大于服务端处理耗时,排队就是主要贡献者。

这一步的结论直接决定动作是扩容实例、拆分流量,还是降低单个查询的 CPU 开销。

索引参数:HNSW 的 ef_search 与 M

如果服务端实际执行检索的耗时变长了,需要审视索引查询的开销。在以 HNSW 为索引算法的实现中(包括常见的 ANN 检索库和多数向量数据库),两个参数直接影响查询延迟:

  • ef_search:查询时探索的候选节点数。值越大,搜索范围越广、召回越充分,但单次查询的 CPU 开销随之增加;
  • M:构图时每个节点的最大邻居数。M 越大,图越密,索引体积和内存占用越大,高并发下还可能影响缓存命中。

从机制上看,ef_search 对高并发延迟的影响更直接:单次查询增加的开销,在 QPS 放大后会变得非常可观。可执行的验证方法是:固定压测 QPS,逐步降低 ef_search(例如每次减半),同时记录 p99 延迟与召回率。如果延迟明显下降且召回率满足业务要求,说明原参数确实偏高。

需要特别注意:ef_search 与 M 的默认值、取值范围和生效方式取决于具体向量数据库的实现。调优前需要查对应产品的官方文档,不能把一套经验值直接套到所有实现上。任何参数调整都应该以“延迟 + 召回率”两个指标共同评估,只盯着延迟调参容易牺牲检索质量。

资源层:CPU、内存与网络

  • CPU:HNSW 查询是 CPU 密集操作。高并发下先确认 CPU 是否打满,如果打满,降低 ef_search 或扩展实例比调整连接池更有效;
  • 内存:索引需要常驻内存。内存紧张时会触发 GC 停顿或换页,表现为查询耗时的周期性尖刺。把内存曲线与 GC 日志放在同一时间轴上,确认尖刺是否与 GC 重合;
  • 网络:跨可用区或跨地域部署时,网络 RTT 本身就是成本。高并发下还要确认网卡带宽、连接数限制是否成为瓶颈。

数据层:分段、合并与删除

采用分段存储的向量库,在线写入和删除会产生多个数据段,查询时需要跨段扫描。延迟突增的时间点如果与大批量写入、删除或后台合并任务重合,需要重点检查段数量是否异常增长、是否存在大量待合并数据、删除操作产生的墓碑记录是否拖慢查询。这类问题光靠调参数解决不了,需要从写入策略和数据清理节奏入手。

可落地的排查顺序

  1. 用链路追踪或日志把耗时拆到连接等待、网络、服务端处理各段;
  2. 确认向量化与重排环节没有劣化;
  3. 检查客户端超时与重试放大;
  4. 检查连接池活跃连接数与获取连接的等待时间;
  5. 检查服务端排队情况,必要时扩容;
  6. 固定 QPS 做 ef_search 梯度压测,同时观察延迟与召回率;
  7. 确认 CPU、内存、网络是否成为瓶颈;
  8. 检查数据段规模与合并状态。

这套顺序的核心原则是:每一次调整只改变一个变量。高并发下的检索延迟劣化很少是单一原因,跳过定位直接调参数或加连接池,往往只是把瓶颈推到了下一层。