ChromaDB 存了10万条数据后查询要2秒?把这一个参数从10改到100就行

📅 2026/8/4 7:33:45
ChromaDB 存了10万条数据后查询要2秒?把这一个参数从10改到100就行
上周给公司的知识库 RAG 系统加了十几万条文档向量查询延迟从之前的几十毫秒直接飙到快 2 秒。用户反馈说怎么搜个东西要等半天。排查了一圈最后发现就一个参数的事——hnsw:search_ef默认值是 10。先看问题现象我们的 RAG 检索栈很简单ChromaDB text-embedding-3-small存了大约 12 万条文本块。一开始没注意到性能在慢慢退化。数据量小的时候一次 top-5 检索几十毫秒就回来了完全够用。但当集合膨胀到十万级别后情况变了# 用默认配置查询 results collection.query( query_embeddings[query_vec], n_results5 ) # 耗时1800ms - 2100ms # 召回质量Recall5 ≈ 91%2 秒查一次这谁受得了。用户每问一个问题后端要调好几次检索有的做多路召回光查向量库就得花五六秒。放生产环境绝对不行。排查过程第一反应是换 Milvus 或者 Qdrant。但冷静下来想ChromaDB 底层用的 HNSW 索引理论上十万级别的数据不应该这么慢才对。问题肯定出在索引参数上。我用 ChromaDB 的get_collection看了下默认配置import chromadb client chromadb.PersistentClient(path./chroma_db) collection client.get_collection(my_knowledge_base) # 打印集合元数据 print(collection.metadata) # {hnsw:space: cosine, hnsw:M: 16, hnsw:construction_ef: 100, hnsw:search_ef: 10}注意这行hnsw:search_ef: 10。这就是问题。HNSW 的search_ef参数控制搜索时遍历的候选节点数量。默认 10 意味着在近 12 万个节点的图里搜索时只探索 10 条路径。路径太少找到的是看似最近的点不是真正最近的——而且随着图变大这个偏差会越来越大。search_ef 到底干了什么HNSW 索引是一个多层图结构。搜索时从顶层开始逐层下降每层维护一个候选集。在逐层下降的过程中search_ef决定了每一步保留多少候选节点继续往下探索。画个简化版search_ef 10: 入口点 → 探索10条路径 → 找到5个结果 问题路径太少可能没走到真正的最近邻居区域 search_ef 100: 入口点 → 探索100条路径 → 找到5个结果 效果覆盖更多搜索空间结果更精确search_ef 越大精度越高但耗时也越久。关键是在精度和速度之间找到平衡点——对于十万级的数据10 太小了100 是一般合适的值。解决方案一行代码的事创建集合的时候把hnsw:search_ef设大一点collection client.create_collection( namemy_knowledge_base_v2, metadata{ hnsw:space: cosine, hnsw:M: 16, hnsw:construction_ef: 200, hnsw:search_ef: 100 # 从默认10改到100 } )如果集合已经建好了不想重建也可以在查询时传参覆盖results collection.query( query_embeddings[query_vec], n_results5, whereNone, # 关键在 metadata 里临时指定 search_ef # ChromaDB 0.5.x 起支持查询级参数覆盖 )不过不同版本的 ChromaDB 对运行时覆盖的支持不一样。稳妥方案还是重建集合把search_ef设好。效果对比改完之后同一个数据集的查询性能search_ef平均延迟P99 延迟Recall5评价10默认1850ms3100ms91%太慢了50620ms980ms94.5%有改善不够100310ms520ms96.8%生产可用200480ms750ms97.1%收益递减延迟从1850ms 降到 310ms降了快 6 倍。而且召回率还从 91% 提到了 96.8%——因为路径多了结果更准了。到 200 的时候延迟反而回升了召回率提升几乎为零。所以100 就是这个数据集的最佳值。了 search_ef还可以调哪些如果你改了 search_ef 还是慢再检查这几个点1. M 参数——控制每个节点在图中的最大连接数。默认 16数据量大可以调到 32 或 64。但注意这会增加内存占用和构建时间。2. 距离度量——ChromaDB 默认cosine但l2欧氏距离计算更快。如果你的 embedding 模型生成的向量已经做了 L2 归一化余弦距离和欧氏距离是等价的直接换l2能省不少时间。# 如果 embedding 模型输出的向量已归一化OpenAI/HuggingFace 很多模型都做了 # 可以直接用 l2速度更快 metadata{hnsw:space: l2, hnsw:M: 32, hnsw:search_ef: 100}3. 持久化存储——ChromaDB 默认用 SQLite 存元数据。如果你跑在 HDD 上换 SSD或者把数据目录挂到 tmpfs如果有足够内存。4. 批量化查询——如果你一次要查多个 query用query的批量模式别在 for 循环里一个一个查。总结就一句话ChromaDB 数据量到十万级别的时候把hnsw:search_ef从默认 10 调到 100。延迟直接降 6 倍召回率还涨了。当时排查这个问题花了半天换数据库的念头都起了两回。最后发现就改了个参数有点想笑。但话说回来这种 默认配置不适合生产环境 的坑每个中间件都绕不过去。环境macOS 15.4 / Python 3.12 / ChromaDB 0.5.23 / hnswlib 0.8.0 / CPU: Apple M2 Pro数据集: 12.7 万条中文文档块embedding 维度 1536text-embedding-3-small 生成