报错是这么冒出来的。pgvector 0.7 上给 80 万条 1024 维向量建 HNSW,top10 召回只有 0.71,把 ef_search 从默认 40 调到 200 后回到 0.96,但单次查询从 9ms 涨到 31ms。
中间走过弯路,把「向量没归一化,余弦距离与内积口径不一致」当成主因,改完现象没变。
原因不止一个,得分开看。ef_search 小于候选集规模,搜索提前收敛。还有一种是建索引时 m 与 ef_construction 取值偏小。
不容易想到的是,过滤条件在检索后才生效,候选被过滤掉一半。
第一件事是用小样本算出基线召回率
python eval_recall.py --db rag --table chunks --k 10 --n 500
再往下对会话调高 ef_search 观察曲线
python -c "import psycopg;psycopg.connect('dbname=rag').execute('set hnsw.ef_search = 200')"
接着重建索引并加大 ef_construction
python -c "import psycopg;psycopg.connect('dbname=rag').execute('reindex index chunks_vec_idx')"
等前面都确认过,再确认写入前向量已做 L2 归一化
python -c "import numpy as np;v=np.random.rand(1024);print(np.linalg.norm(v/np.linalg.norm(v)))"
怎么算真的好了,看的是同一批现象。召回率稳定在 0.95 以上,P99 查询在 35ms 以内,召回与延迟的取舍有了可复现的曲线。
容易忽略的是,ef_search 是查询期的旋钮,改它不用重建索引,先在会话里试出合适值再写进连接初始化语句。报错文案容易误导,真正的原因往往在相邻那一层的日志里。
