一文读懂 Elasticsearch 向量搜索:从概念到实践的深度解析
引言:搜索技术的演进与向量的崛起
在数字世界的浩瀚信息海洋中,搜索是用户发现、获取知识和连接信息的核心方式。传统的基于关键词的搜索技术,例如倒排索引,在过去几十年里发挥了巨大作用。然而,随着数据形态的日益多样化(文本、图片、音频、视频等)以及用户对搜索结果“理解”能力的更高要求,传统搜索的局限性逐渐显现。
考虑一个场景:用户搜索“美味的意大利面”。传统的关键词搜索可能会匹配包含“美味”、“意大利面”的文档。但如果一篇文章描述了“令人垂涎的意式宽面”,即使没有直接包含“美味的意大利面”,它可能才是用户真正想要的内容。传统搜索难以理解词语之间的深层含义、上下文关联以及不同模态数据的内容相似性。
这时,向量搜索应运而生。向量搜索(Vector Search),也被称为相似性搜索(Similarity Search)或最近邻搜索(Nearest Neighbor Search),其核心思想是将不同类型的数据(文本、图片、声音等)通过机器学习模型转化为高维空间中的数值向量(称为嵌入,Embeddings)。这些向量捕捉了原始数据的语义或特征信息。具有相似含义或内容的原始数据,在向量空间中也会距离较近。通过计算向量之间的距离或相似度,我们就可以找到与查询向量最相似的数据,从而实现基于内容或语义的搜索。
Elasticsearch,作为全球领先的分布式搜索和分析引擎,自然不会错过这一重要的技术趋势。自7.x版本开始引入对dense_vector
字段的支持,并在8.x版本中显著增强了向量搜索能力,特别是集成了高效的近似最近邻(ANN)算法,使得在Elasticsearch中进行高性能的向量搜索成为现实。这使得用户可以将传统的结构化、半结构化数据搜索与基于语义的向量搜索无缝结合,构建出更加智能、精准、多模态的搜索应用。
本文将带你深入了解 Elasticsearch 中的向量搜索,从基本概念到实际操作,从底层原理到性能优化,力求让你“一文读懂”这一强大的功能。
第一部分:向量搜索基础知识
1. 什么是嵌入(Embeddings)?
嵌入是将非数值数据(如文本、图像、声音等)转换为固定长度数值向量的过程。这个转换通常由预训练或微调的机器学习模型完成。这些模型学习数据的内在模式和关系,并将这些模式压缩到向量中。
例如:
* 对于文本,使用BERT、GloVe、Word2Vec、Sentence-BERT等模型可以将句子或段落转化为向量。语义相似的句子(如“狗在跑”和“小狗奔跑”)会生成在向量空间中距离很近的向量。
* 对于图像,使用ResNet、Vision Transformer等模型可以将图片转化为向量。视觉内容相似的图片(如不同角度拍摄的同一只猫)会生成距离很近的向量。
* 对于声音,可以使用相应的声学模型生成向量。
向量的维度(Dimensions)取决于所使用的模型,通常在几十到几千维之间。维度越多,理论上可以编码的信息越多,但同时也会增加存储和计算的开销。
2. 向量空间与相似度度量
将数据转化为向量后,它们就存在于一个高维向量空间中。在这个空间中,我们通过计算向量之间的“距离”或“相似度”来衡量原始数据的关联性。常用的相似度度量方法包括:
- 欧几里得距离(Euclidean Distance / L2 Distance):最直观的距离度量,即两点在空间中的直线距离。距离越小,相似度越高。公式:$d(\mathbf{v}1, \mathbf{v}_2) = \sqrt{\sum{i=1}^{d} (\mathbf{v}{1i} – \mathbf{v}{2i})^2}$
- 余弦相似度(Cosine Similarity):衡量两个向量方向的相似性,不受向量长度的影响。它计算的是两个向量夹角的余弦值。值域在 -1 到 1 之间。余弦值越接近 1,表示方向越一致,相似度越高;接近 -1 表示方向相反,相似度越低;接近 0 表示相互正交,相似度较低。通常用于测量文本向量的相似度。公式:$similarity(\mathbf{v}_1, \mathbf{v}_2) = \frac{\mathbf{v}_1 \cdot \mathbf{v}_2}{||\mathbf{v}_1|| \cdot ||\mathbf{v}_2||}$
- 点积(Dot Product / Inner Product):两个向量的内积。如果向量已经进行过单位化(长度为1),则点积与余弦相似度相等。如果向量未单位化,点积会同时考虑方向和长度。公式:$dot_product(\mathbf{v}1, \mathbf{v}_2) = \sum{i=1}^{d} \mathbf{v}{1i} \cdot \mathbf{v}{2i}$
Elasticsearch 支持这些常见的相似度度量,允许用户根据其使用的嵌入模型和具体场景选择最合适的度量方式。
3. 最近邻搜索(Nearest Neighbor Search, NNS)
向量搜索的核心任务是给定一个查询向量,在大批量的目标向量中找到与其“最近”(即相似度最高,或距离最近)的 K 个向量。这就是 K 近邻搜索(k-Nearest Neighbor Search, kNN)。
在高维空间中精确计算 K 个最近邻居的计算成本非常高昂,尤其是在数据量巨大时。遍历所有向量并计算距离是不可行的(称为暴力搜索或精确 kNN)。因此,在实际应用中,通常使用近似最近邻(Approximate Nearest Neighbor Search, ANNS)算法。ANN算法通过牺牲一定的精度来换取查询速度的大幅提升。它们通过构建特殊的数据结构(如树、图或哈希表)来加速搜索过程。Elasticsearch 使用的是 HNSW (Hierarchical Navigable Small World) 图算法来实现高效的 ANNS。
第二部分:Elasticsearch 如何支持向量搜索
Elasticsearch 对向量搜索的支持主要体现在以下几个方面:
1. dense_vector
字段类型
Elasticsearch 引入了 dense_vector
字段类型来存储向量数据。这是一个数值数组,代表了高维空间中的一个点。定义 dense_vector
字段时,必须指定向量的维度(dims
参数)。
映射示例:
json
PUT my-vector-index
{
"mappings": {
"properties": {
"text_embedding": {
"type": "dense_vector",
"dims": 768, // 例如,使用一个生成768维向量的模型
"index": true, // 启用向量索引,用于ANN搜索
"similarity": "cosine" // 指定默认的相似度度量
},
"image_embedding": {
"type": "dense_vector",
"dims": 512, // 例如,另一个生成512维向量的模型
"index": true,
"similarity": "l2"
},
"title": {
"type": "text" // 也可以存储原始文本或其他元数据
},
"url": {
"type": "keyword"
}
}
}
}
type: "dense_vector"
:指定字段类型为密集向量。dims
: 必须指定向量的维度。index: true
: 关键参数。启用向量索引。Elasticsearch 会为这个字段构建 HNSW 图,从而支持高效的近似最近邻搜索。如果设置为false
(默认值),则只能进行精确的暴力 kNN 搜索,这通常非常慢,不适合生产环境。similarity
: 指定用于衡量向量相似度的算法 (l2
,cosine
,dot_product
)。这会影响 Elasticsearch 计算相似度分数的方式。
2. 索引向量数据
将原始数据(如文本、图片)转换为向量后,就可以将这些向量作为 dense_vector
字段的值索引到 Elasticsearch 文档中。
索引示例:
“`json
POST my-vector-index/_doc/1
{
“title”: “一篇关于人工智能的文章”,
“text_embedding”: [0.1, 0.5, …, 0.9], // 768维向量
“image_embedding”: [0.3, 0.7, …, 0.2], // 512维向量
“url”: “http://example.com/article1”
}
POST my-vector-index/_doc/2
{
“title”: “深度学习技术的最新进展”,
“text_embedding”: [0.15, 0.48, …, 0.85], // 语义相似的向量
“image_embedding”: [0.32, 0.71, …, 0.25],
“url”: “http://example.com/article2”
}
// … 更多文档
“`
请注意,向量本身是数值数组,需要你的应用程序或ETL管道负责生成这些向量(通常通过调用机器学习模型的推理服务)。Elasticsearch 只负责存储和搜索这些向量。
3. 进行向量搜索 (kNN 查询)
Elasticsearch 提供了 knn
查询来执行向量搜索。这个查询类型与传统的基于倒排索引的查询(如 match
, term
, range
等)有所不同,它专门用于在 dense_vector
字段上执行 ANNS。
kNN 查询示例:
json
GET my-vector-index/_search
{
"knn": {
"field": "text_embedding", // 指定要搜索的向量字段
"query_vector": [0.12, 0.51, ..., 0.88], // 用户的查询向量 (例如,由用户输入的文本生成)
"k": 10, // 指定返回最相似的文档数量
"num_candidates": 100, // 指定算法内部需要考虑的候选数量
"boost": 1.0 // 查询权重 (与传统查询结合时有用)
},
"_source": ["title", "url"] // 指定返回的字段
}
field
: 指定要进行向量搜索的dense_vector
字段。query_vector
: 用户的查询向量。这是通过将用户的查询(如文本、图片)输入到与索引时相同的机器学习模型中生成的向量。k
: 指定需要返回的最相似文档数量。这是用户最终在搜索结果中看到的数量。num_candidates
: 关键参数,用于控制 ANNS 算法(HNSW)在查找 k 个最近邻时需要探索的内部候选邻居数量。num_candidates
的值越大,搜索结果的召回率(Recall)越高,越接近精确 kNN 的结果,但查询延迟也会越高。反之,值越小,查询速度越快,但可能会牺牲一定的召回率。合理设置num_candidates
是调优向量搜索性能的关键。通常num_candidates
应该大于或等于k
。经验上,num_candidates
可以设置为k
的几倍。boost
: 与传统查询一样,用于调整查询的相对权重。
4. HNSW (Hierarchical Navigable Small World) 图算法
Elasticsearch 8.0+ 版本通过集成 HNSW 算法极大地提升了向量搜索的性能。HNSW 是一种高效的 ANNS 算法,它构建了一个多层的图结构来组织向量。
- 结构: HNSW 图包含多个层。顶层包含的节点较少,节点之间的连接跨度较大,用于快速导航到目标向量的大致区域。底层包含所有节点,连接更密集,用于在局部区域进行精确搜索。
- 搜索过程: 从顶层随机一个入口点开始,算法在当前层寻找距离查询向量最近的邻居,然后移动到那个邻居。重复这个过程,直到无法在当前层找到更近的邻居为止。然后,算法下降到下一层,以当前找到的最优节点作为入口点,重复搜索过程。直到达到最底层,找到的节点集合中最接近查询向量的 k 个节点就是结果。
- 优点: HNSW 在搜索速度和召回率之间提供了很好的平衡,并且在索引构建和搜索过程中都具有较好的可扩展性。构建 HNSW 图(
index: true
)是索引向量数据时需要额外的时间和空间开销,但这是实现高性能向量搜索的基础。
Elasticsearch 的 HNSW 实现允许配置一些高级参数,例如 m
(每个节点的最大连接数) 和 ef_construction
(索引构建时的搜索范围),这些参数可以进一步影响索引时间和查询性能/召回率的权衡,但对于大多数用户而言,使用默认值或调整 num_candidates
已经足够。
第三部分:结合传统搜索与向量搜索(混合搜索)
Elasticsearch 的一个重要优势在于它是一个统一的平台,可以同时处理结构化数据、非结构化文本以及向量数据。这意味着我们可以轻松地将传统的基于关键词、范围、过滤等查询与向量相似性查询结合起来,实现强大的混合搜索(Hybrid Search)。
混合搜索通常用于以下场景:
- 过滤向量搜索: 先使用传统查询(如
term
或range
)过滤出一部分文档,然后只在这些过滤后的文档子集上执行向量搜索。例如:“找到发布日期在最近一个月内的语义上与’新能源汽车政策’相似的文章”。 - 向量搜索加过滤: 先执行向量搜索找到一批相似的文档,然后对这些文档进行传统过滤。例如:“找到与这张图片相似的商品中,价格低于100元且库存大于0的”。
- 联合评分: 同时执行传统查询和向量查询,并将它们的得分以某种方式组合起来,得到最终的排序结果。例如:“找到既包含关键词’Python’又在语义上与’数据科学库’相关的文档”。
在 Elasticsearch 中,实现混合搜索通常是将 knn
查询与其他查询类型放在同一个 _search
请求体中。
混合搜索示例(过滤向量搜索):
json
GET my-vector-index/_search
{
"query": {
"bool": {
"filter": [ // 使用filter,过滤不影响相关性得分
{
"range": {
"publish_date": {
"gte": "now-1M/d" // 过滤最近一个月发布的文档
}
}
},
{
"term": {
"status": "published" // 过滤已发布的文档
}
}
]
}
},
"knn": {
"field": "text_embedding",
"query_vector": [0.12, 0.51, ..., 0.88],
"k": 10,
"num_candidates": 100
},
"_source": ["title", "url", "publish_date"]
}
在这个例子中,Elasticsearch 会首先执行 query
部分的 filter
查询,筛选出符合条件的文档。然后,只在这些过滤后的文档集合上执行 knn
向量搜索,找到最相似的 10 个结果。这种方式非常高效,因为它减少了向量搜索的搜索空间。
混合搜索示例(联合评分 – 使用 Reciprocal Rank Fusion, RRF):
Elasticsearch 8.9 引入了对 Reciprocal Rank Fusion (RRF) 的原生支持,这是一种将来自不同搜索结果集的排序进行融合的算法。它可以将传统查询结果和向量查询结果的排名有效地结合起来。
json
GET my-vector-index/_search
{
"rank": {
"rrf": {
"window_size": 50, // 考虑每个查询结果的前50名
"rank_constant": 60 // 调节因子,越大越强调低排名结果
}
},
"query": {
"match": { // 传统关键词搜索
"title": "python data science"
}
},
"knn": { // 向量搜索
"field": "text_embedding",
"query_vector": [0.12, 0.51, ..., 0.88], // 对应 "python data science" 语义的向量
"k": 10, // knn查询本身返回的文档数,RRF会在此基础上工作
"num_candidates": 100
},
"_source": ["title", "url"]
}
在这个例子中,Elasticsearch 会并行执行 query
部分的传统 match
查询和 knn
部分的向量查询。然后,使用 RRF 算法将这两个结果集的排序进行融合,生成一个最终的混合排序结果。RRF 通过考虑每个文档在不同结果集中的排名来计算一个融合分数,排名越靠前,分数越高。这种方法在很多场景下比简单地叠加分数更能提升相关性。
第四部分:实现与优化细节
1. 映射配置 (dims
, index
, similarity
)
dims
: 必须与你使用的嵌入模型输出的维度一致。不一致会导致索引错误。index: true
: 强烈推荐设置为 true,以启用 HNSW 索引。这会显著提升查询性能。设置为false
只适用于非常小的数据集或需要精确 kNN 的特殊场景(通常非常慢)。similarity
: 选择合适的相似度度量非常重要。如果你的向量已经进行了 L2 归一化(即向量长度为1),那么cosine
和dot_product
会得到相同的排序结果,选择哪个都可以。如果你的向量没有归一化,并且向量的长度也包含重要信息(例如,某些模型生成的向量长度反映了输入的某种特征),那么dot_product
或l2
可能更合适。如果不确定,通常cosine
是一个安全的默认选择,因为它对向量长度不敏感,更侧重方向上的相似性。请查阅你的嵌入模型的文档,了解推荐的相似度度量以及是否进行了归一化。
2. HNSW 索引参数 (index.knn.engine.hnsw.*
)
在创建 dense_vector
字段的 mapping 时,可以通过 index_options
参数配置 HNSW 的一些高级参数:
json
PUT my-vector-index
{
"mappings": {
"properties": {
"text_embedding": {
"type": "dense_vector",
"dims": 768,
"index": true,
"similarity": "cosine",
"index_options": {
"type": "hnsw",
"m": 16, // 每个节点的最大连接数 (影响索引大小, 搜索精度/速度)
"ef_construction": 100 // 索引构建时的搜索范围 (影响索引时间/精度)
}
}
}
}
}
* m
: 控制构建图时每个节点连接的邻居数量。较大的 m
会生成更密集的图,通常能提高搜索精度和召回率,但会增加索引时间、索引大小和内存使用。
* ef_construction
: 控制构建图时搜索邻居的范围。较大的 ef_construction
会生成质量更高的图,通常能提高搜索精度和召回率,但会显著增加索引时间。
这些参数是构建索引时的配置,一旦索引完成就不能更改。默认值通常是一个不错的起点,可以根据实际需求和硬件资源进行调优。
3. 查询参数 (k
, num_candidates
)
k
: 用户期望返回的结果数量,直接影响响应大小。num_candidates
: 最重要的调优参数,影响查询性能和召回率。- 增加
num_candidates
会考虑更多内部候选,从而提高召回率,但会增加计算量和延迟。 - 减少
num_candidates
会减少计算量,降低延迟,但可能漏掉一些相关的结果。 - 建议从一个合理的起始值开始(例如
k
的几倍,如 50 或 100),然后根据实际测试结果(召回率要求和延迟目标)进行调整。可以通过 Elasticsearch 的 Profile API 来分析查询执行的详细情况。
- 增加
4. 内存考虑
存储和搜索向量需要消耗内存。dense_vector
字段的向量数据以及 HNSW 图结构都存储在 JVM 堆外内存(Native Memory)中,以提高访问速度。向量维度越高、文档数量越多,所需的内存就越多。
粗略估算内存需求:
每个向量的存储空间(字节)≈ dims
* 4 (对于 float
) + HNSW 图开销。
HNSW 图开销取决于 m
和文档数量。一个简单的估算方法是,每个向量的 HNSW 索引开销大约是 m
* 4 * 2 (对于双向连接) * 4 (字节) + 其他开销。
总内存需求 ≈ 文档数量 * (向量大小 + HNSW 开销)。
例如,1000万个 768维向量,m=16
:
每个向量大小:768 * 4 = 3072 字节 (~3 KB)
HNSW 开销估算:16 * 2 * 4 ≈ 128 字节/连接。每个节点有 m
个连接,但内部实现更复杂。一个更经验性的估算可能是每个向量的 HNSW 索引需要几十到几百字节不等,取决于维度和 m
。假设 HNSW 开销是每个向量 200 字节。
总内存 ≈ 10^7 * (3072 + 200) 字节 ≈ 10^7 * 3272 字节 ≈ 32.7 GB。
这是一个非常粗略的估算,实际内存使用会受 JVM 设置、操作系统、Elasticsearch 版本和具体数据特征影响。在部署前,务必进行详细的内存规划和实际负载测试。如果内存不足,可能会导致节点不稳定甚至崩溃。
5. 性能调优
- 硬件: 为 Elasticsearch 节点配置足够的内存(特别是堆外内存)和 CPU 是向量搜索性能的基础。使用 SSD 硬盘也很重要。
- 分片策略: 合理规划分片数量。过多的分片会增加协调开销,过少的分片可能导致单个分片过大或无法充分利用集群资源。确保分片大小适中(通常建议在 10GB 到 50GB 之间,包含向量数据)。
num_candidates
: 这是最重要的查询侧调优参数,根据召回率和延迟需求进行调整。- HNSW 参数 (
m
,ef_construction
): 在索引构建时进行调优,影响索引构建时间和查询性能/召回率的权衡。调优这些参数通常需要更多的实验。 - 批量索引: 向量索引构建是计算密集型的,使用批量索引可以提高效率。
- 监控: 密切监控 Elasticsearch 的内存使用、CPU 使用、搜索延迟和索引延迟等指标。特别是注意 JVM 堆外内存的使用情况。
第五部分:向量搜索的典型应用场景
Elasticsearch 的向量搜索能力使其能够支持广泛的智能搜索应用:
- 语义搜索: 基于文本内容的语义相似性进行搜索,而不是简单的关键词匹配。用户可以用自然语言提问,找到语义相关的文档。例如,搜索“如何烤美味的鸡肉”,可能找到关于“家庭烹饪烤鸡技巧”的文章。
- 图片搜索: 将图片转化为向量后,可以实现“以图搜图”或基于文本描述的图片搜索(如果文本和图片向量在同一个空间中)。
- 推荐系统: 找到与用户正在浏览/购买的物品相似的其他物品(基于物品描述、图片、用户行为等向量)。
- 问答系统: 找到与用户问题语义最相似的已知问题或包含答案的文本片段。
- 异常检测: 在某些场景下,远离大多数其他向量的向量可能代表异常数据。
- 重复内容检测: 查找向量距离非常近的文档,可能表示内容高度相似或重复。
- 多模态搜索: 将不同类型的数据(如商品的图片和描述)映射到同一个向量空间,实现跨模态搜索。例如,用商品的图片搜索其描述,或者用商品的描述搜索其图片。
结论:Elasticsearch 向量搜索的未来与展望
Elasticsearch 提供的向量搜索功能,特别是与 HNSW 的集成以及对混合搜索的良好支持,使其成为构建下一代智能搜索应用的一个强大且有吸引力的平台。对于许多已经在使用 Elasticsearch 的组织而言,在其现有数据和基础设施之上增加向量搜索能力,无疑是实现语义搜索和多模态搜索最便捷的方式之一。
当然,向量搜索仍然是一个快速发展的领域。机器学习模型不断进步,生成更高质量的向量;向量数据库技术也在持续演进。Elasticsearch 也在不断增强其向量搜索能力,例如未来可能支持更多的 ANNS 算法、提供更精细的 HNSW 参数控制、优化内存管理以及与其他机器学习工具的集成。
对于希望在自己的应用中引入向量搜索的开发者和企业来说,Elasticsearch 提供了一个集成度高、功能丰富的解决方案。通过理解 dense_vector
字段、kNN 查询、HNSW 原理以及如何结合传统搜索,你可以充分利用 Elasticsearch 的力量,构建出更加智能、高效和用户友好的搜索体验。
现在,你已经基本“一文读懂”了 Elasticsearch 的向量搜索。是时候在实践中探索它的强大能力了!