Elasticsearch 8.13.2,dense_vector 1024 维,索引 100 万文档,knn 查询没写 num_candidates,recall@10 只有 0.61,调大 k 也没用。
一开始怀疑是「分片数过多,每片候选合并后仍不足」,查了一圈,方向不对。
排查方向其实有限,先把可能性排一排。num_candidates 默认按 k 的 1.5 倍算,候选太少。也可能是向量写入前没有归一化,cosine 相似度被压缩。
另外,索引段没预热,候选搜索大量落盘。
第一件事是看文档数与分片数
curl -s localhost:9200/docs/_stats?pretty | jq '.indices.docs.primaries.docs.count'
显式给足候选数再查
curl -s -X POST localhost:9200/docs/_search -H 'Content-Type: application/json' -d '{"knn":{"field":"vec","query_vector":[0.1,0.2],"k":10,"num_candidates":200}}'
随后重算这一版的召回
python eval_recall.py --gt gt_es.jsonl --pred es_pred.jsonl --k 10
强制合并段后再压一次
curl -s -X POST localhost:9200/docs/_forcemerge?max_num_segments=1
隔了一天再看,现象没变,说明不是偶发。
那几天正好在做别的变更,先把时间线对齐才敢动手。
排查用的命令都是临时敲的,没有写成脚本,事后才整理。
恢复的判定其实很直接。recall@10 从 0.61 升到 0.93,查询延迟从 22 毫秒涨到 45 毫秒,仍在预算内。
容易忽略的是,kNN 的召回由候选数决定而不是 k 决定,调大返回条数找不回被漏掉的近邻。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。
