efSearch 是什么
HNSW 是图索引:查询时从入口点出发在图上贪心游走,efSearch 决定候选队列的长度——队列越长,越有机会跳出局部最优、越接近精确结果,同时耗时也越长。它是典型的召回与延迟旋钮,调法不是凭感觉取一个值,而是从 32 或 64 起测,画出召回率与延迟随参数变化的曲线,在召回开始变缓的地方停下。取值要写进部署配置固化,避免每次发布都被顺手改动。
基本信息
| 作用 | HNSW 查询阶段的候选队列长度 |
|---|---|
| 影响 | 数值越大召回越高、延迟越高 |
| 调优起点 | 32 或 64,按召回曲线找拐点 |
| 区分参数 | efConstruction 管构建,efSearch 管查询,改错位置不生效 |
| 固化方式 | 写入服务配置并纳入回归评测 |
典型使用场景
- 检索质量不达标:先看提高 efSearch 能否补回召回,判断是索引参数还是数据问题
- 延迟超标:在不明显掉召回的前提下逐档下调,找到可接受的平衡点
- 索引选型对比:同一份数据在 HNSW 与 IVF 上做等召回延迟对比
- 版本回归:参数改动后必须跑评测集,避免“调快了但答错了”
- 容量规划:按不同 efSearch 档位实测 QPS 与 p95 延迟,作为扩容依据
用召回率选 efSearch
用召回率选 efSearch
import faiss, numpy as np
d = 128
xb = np.random.rand(50000, d).astype('float32')
q = np.random.rand(200, d).astype('float32')
flat = faiss.IndexFlatL2(d); flat.add(xb)
_, exact = flat.search(q, 10) # 精确 top-10 作为基线
index = faiss.IndexHNSWFlat(d, 32); index.add(xb)
for ef in (16, 32, 64, 128, 256):
index.hnsw.efSearch = ef
_, got = index.search(q, 10)
print(ef, recall_at_k(exact, got, 10)) # 记录 (ef, 召回, 平均延迟)Qdrant 查询期参数
Qdrant 查询期参数
res = client.query_points(
collection_name='kb', query=query_vec, limit=10,
search_params=models.SearchParams(hnsw_ef=128)) # 仅在本次查询生效
# 想要全局档位则在集合的 hnsw_config 中设置参数与收益
| efSearch | 召回 | 延迟 | 适用 |
|---|---|---|---|
| 16~32 | 偏低 | 最低 | 高并发、允许近似 |
| 64~128 | 接近精确 | 中等 | 大多数生产场景的默认档 |
| 256+ | 基本等于精确 | 明显上升 | 离线评测或低 QPS 场景 |
| 不设(用默认) | 取决于实现默认值 | — | 上线前必须显式确认 |
efSearch 只影响查询侧,调它不需要重建索引,所以适合作为线上应急手段;但要把它固化成配置并记录版本,否则每次发布都可能悄悄换档。