Elasticsearch 向量检索实战:dense_vector 字段配置与 kNN 查询
Elasticsearch 从 7.x 版本开始原生支持向量字段和 kNN(k 最近邻)检索,不需要额外插件就能做语义相似度搜索。
如果你的版本不低于 7.10,可以直接用 dense_vector 字段类型存储向量,再用 kNN 查询接口检索。
下面按实际操作顺序说明配置和验证方法。
确认版本与前置条件
先通过以下命令确认集群版本和节点状态:
curl -X GET "localhost:9200"
curl -X GET "localhost:9200/_cat/nodes?v"
返回的 version.number 字段就是当前版本。dense_vector 的 kNN 检索需要 7.10 及以上版本,如果版本较低,需要先升级。
另外,向量维度要和后续写入的数据严格一致,否则会报维度不匹配错误。
创建带向量字段的索引
假设用 3 维向量做演示,实际场景中常见的是 768 维或 1536 维。
创建索引时指定 dense_vector 字段和维度:
curl -X PUT "localhost:9200/vector_demo" -H 'Content-Type: application/json' -d '
{
"mappings": {
"properties": {
"title": { "type": "text" },
"embedding": {
"type": "dense_vector",
"dims": 3,
"index": true,
"similarity": "cosine"
}
}
}
}'
关键参数说明:
dims:向量维度,必须和写入数据一致。index: true:开启向量索引,kNN 查询才能生效。similarity:相似度算法,可选cosine、dot_product、l2_norm。做语义搜索通常用cosine。
写入向量数据并验证
写入两条示例文档:
curl -X POST "localhost:9200/vector_demo/_doc/1?refresh=true" -H 'Content-Type: application/json' -d '
{ "title": "苹果手机", "embedding": [0.9, 0.1, 0.0] }'
curl -X POST "localhost:9200/vector_demo/_doc/2?refresh=true" -H 'Content-Type: application/json' -d '
{ "title": "香蕉", "embedding": [0.1, 0.9, 0.2] }'
写入后用 _search 查询确认数据已存在:
curl -X GET "localhost:9200/vector_demo/_search?pretty" -H 'Content-Type: application/json' -d '
{ "query": { "match_all": {} } }'
如果能看到两条文档且 embedding 字段完整,说明写入成功。
执行 kNN 相似度查询
用查询向量检索最相似的文档:
curl -X GET "localhost:9200/vector_demo/_search?pretty" -H 'Content-Type: application/json' -d '
{
"knn": {
"field": "embedding",
"query_vector": [0.8, 0.2, 0.0],
"k": 2,
"num_candidates": 10
}
}'
k 是返回结果数量,num_candidates 是每个分片参与计算的候选数量,一般设为 k 的几倍。
返回结果中 _score 越高表示越相似。
如果查询向量和文档向量维度不一致,会直接报错,需要检查写入和查询的维度是否统一。
常见坑与排查
- 维度不匹配:写入或查询时报
dimension mismatch,先核对dims设置和实际数组长度。 - kNN 查询无结果:确认索引映射中
index为true,且查询字段名拼写正确。 - 相似度算法选择:
cosine适合文本嵌入,dot_product要求向量已归一化,l2_norm适合欧氏距离场景。 - 性能影响:向量索引会占用额外内存和磁盘,大规模数据建议先在小数据集上验证
num_candidates对延迟的影响。
验证检索效果
写一个简单的 Python 脚本验证结果是否符合预期:
import requests
query = {
"knn": {
"field": "embedding",
"query_vector": [0.8, 0.2, 0.0],
"k": 2,
"num_candidates": 10
}
}
resp = requests.get("http://localhost:9200/vector_demo/_search", json=query)
for hit in resp.json()["hits"]["hits"]:
print(hit["_source"]["title"], hit["_score"])
预期输出中“苹果手机”的分数应高于“香蕉”。
如果结果不符合直觉,先检查向量是否来自同一个嵌入模型,不同模型产生的向量不能混用。
按以上步骤操作后,Elasticsearch 向量检索的基本链路就通了。
后续可以结合 script_score 做混合查询,或者调整 similarity 参数来适配不同的嵌入模型。