想给搜索加上"语义"
我们的商品搜索一直靠关键词匹配,用户搜"适合送妈妈的礼物"这种长尾 query 基本搜不到,因为标题里没有这几个字。运营天天抱怨转化差。ES 8 增强了 dense_vector 和 kNN 检索,我试着把商品标题做成向量塞进去,让语义相近的也能召回,点击率明显好转。这里把建模、查询和选型一并记下来。
dense_vector 字段建模
先建一个带向量字段的索引,维度跟模型走(我们用 768 维的 bge 中文模型,本地用 ONNX 推理):
PUT /products
{
"mappings": {
"properties": {
"title": { "type": "text" },
"title_vec": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "cosine"
}
}
}
}
写入时把 title 用模型推理成向量塞进 title_vec。注意 ES 8 的向量默认走 HNSW 近似索引,构建时会多占内存,segment 合并时也有开销。我们 200 万商品,向量字段约占 6GB 堆外,建索引时把 refresh_interval 调大减少合并压力。
kNN 查询
查询时把用户 query 也向量化,用 knn 子句召回最近的 N 个:
GET /products/_search
{
"knn": {
"field": "title_vec",
"query_vector": [0.12, -0.03, ...], // 768 维
"k": 10,
"num_candidates": 100
},
"query": { "match": { "title": "礼物" } }
}
我们还把 kNN 和传统的 BM25 关键词查询用 bool 组合,语义召回和精确匹配互补。实测"送妈妈的礼物"这种 query 的点击率从 11% 升到 34%,长尾 query 的覆盖明显变广。
和专门向量库的差距
也别神话它。我对照测了 Milvus 和纯 ES,在 200 万向量规模下:
| 维度 | ES 8 向量 | Milvus |
|---|---|---|
| 查询延迟(P99) | 23ms | 3ms |
| 写入吞吐 | 受限于分片 | 更高 |
| 运维复杂度 | 复用现有 ES 集群 | 单独一套 |
| 混合检索 | 原生支持 | 需自研 |
结论很清楚:数据量在千万级以内、又要和现有文本检索混合,ES 8 向量是性价比之选;真要做亿级纯向量召回,还是得上 Milvus 或专用向量数据库。我们当前量级和混合检索需求,ES 最省事。
小结
ES 8 的 dense_vector 加 kNN 让"搜索加语义"能在一个系统里完成,省了引入新组件的运维负担。代价是向量检索性能和扩展性不如专业库。选型时先问自己数据规模和要不要混合检索,别盲目追新也别一味守旧。我们已经把语义召回作为主搜的补充通道跑了一个月,稳定。