问题现状

一篇发布在 Juejin 上的详细技术文章剖析了为何 LangChain 生态中最流行的轻量级向量数据库 Chroma 在生产负载下会崩溃。作者指出了三个根本原因:HNSW 索引必须完全驻留在 RAM 中(内存随向量数量线性增长),SQLite 锁竞争限制了即使开启 WAL 模式下的写入吞吐量,以及默认的 LangChain Chroma 客户端是同步的,会在 FastAPI 或任何异步框架中阻塞事件循环。

重要性分析

大多数 RAG 教程止步于三行代码的演示。当 QPS 从 1 攀升至 100,或文档数量突破一百万时,团队都会遭遇同样的瓶颈:add_documents 超时、P99 延迟飙升至数秒,以及服务在运行几天后出现 OOM。文章提出了四项具体修复方案:

  • 将同步 Chroma 调用封装在具有固定池大小(建议 4 个 worker)的 ThreadPoolExecutor 中,而不是直接阻塞异步事件循环
  • 切勿为每个请求实例化新的 Chroma 对象——HNSW 索引加载成本高昂;应复用单例模式
  • 使用 Chroma 的 HTTP 客户端模式(AsyncHttpClient),将索引管理卸载到专用的服务器进程
  • 通过队列进行批量写入以减少 SQLite 锁竞争,而不是对每个传入文档都调用 add_documents

亚太视角

使用本地嵌入模型(如 Qwen 或 BAAI 的 BGE 系列)构建 RAG 产品的中国和东南亚开发者面临一个叠加问题:中文文本分块产生的文档数量高于同等语料库的英文版本,从而加速触及内存上限。部署在 RAM 有限(早期产品常见 2–4 GB 层级)的阿里云或腾讯云实例上的团队,应尽早考虑切换到 Chroma 的客户端 - 服务器模式,或评估支持基于磁盘 HNSW 索引的 Milvus Lite 作为即插即用替代方案。此处描述的异步包装模式也直接适用于与 DashScope 或百度千帆嵌入 API 的集成,这些 API 自身的速率限制也能从受控的并发中受益。

本周行动项

审计您当前的 Chroma 使用情况:在代码库中搜索请求处理器或循环内的 Chroma( 实例化调用。将每个调用移至模块级单例,并使用共享的 ThreadPoolExecutor(max_workers=4) 通过 loop.run_in_executor 包装 similarity_search 调用。使用 locustk6 在 20 个并发用户的负载测试下,测量优化前后的 P99 延迟。