作者:来自 Elastic Ugo Sangiorgi
Elasticsearch BBQ 与 OpenSearch FAISS 的性能对比。
带有二值量化的向量搜索:使用 BBQ 的 Elasticsearch 比使用 FAISS 的 OpenSearch 快 5 倍。Elastic 收到了来自社区的请求,希望澄清 Elasticsearch 与 OpenSearch 在性能上的差异,特别是在语义搜索 / 向量搜索方面,因此我们进行了这些性能测试,以提供清晰、数据驱动的对比。
二进值量化对决
以原始形式存储高维向量会占用大量内存。量化技术通过将这些向量压缩为更紧凑的表示形式,大幅减少内存占用。搜索过程在压缩空间中进行,从而降低计算复杂度,特别是在处理大型数据集时,提高搜索速度。
Elastic 致力于将 Lucene 打造成顶级的向量引擎。我们在 Elasticsearch 8.16 中基于 Lucene 引入了更优的二值量化(BBQ),并在 8.18 和 9.0 中进一步优化。BBQ 基于一种新的标量量化方法,将 float32 维度压缩为比特,实现了约 95% 的内存压缩率,同时保持了较高的排序质量。
另一方面,OpenSearch 使用多个向量引擎:nmslib(现已弃用)、Lucene 和 FAISS。在之前的一篇博客中,我们对 Elasticsearch 和 OpenSearch 的向量搜索进行了比较。我们使用了三个不同的数据集,并在两种产品上测试了不同的引擎组合和配置。
本博客聚焦于两款产品当前可用的二进值量化算法。我们使用 openai_vector 的 Rally track,测试了 Elasticsearch 的 BBQ 与 OpenSearch 使用 FAISS 的二进值量化。
测试的主要目标是在相同召回率水平下评估两种方案的性能。什么是召回率?召回率是衡量搜索系统成功检索到多少相关结果的指标。
在本次评估中,recall@k 尤为关键,其中 k 表示所考虑的前 k 个结果。Recall@10、Recall@50 和 Recall@100 分别衡量在返回的前 10、50 和 100 个结果中,出现了多少真实的相关项。召回率的范围是从 0 到 1(或从 0% 到 100%)。这很重要,因为我们讨论的是近似 KNN(ANN)而不是精确 KNN,在精确 KNN 中召回率总是 1(100%)。
对于每个 k 值,我们还指定了 n,表示在最终排序之前所考虑的候选项数量。这意味着在计算 Recall@10、Recall@50 和 Recall@100 时,系统首先通过二进值量化算法检索 n 个候选项,再对其排序,判断前 k 个结果中是否包含预期的相关项。
通过控制 n,我们可以分析效率与准确率之间的权衡。较高的 n 通常会提升召回率,因为有更多候选项可供排序,但也会增加延迟并降低吞吐量。相反,较低的 n 虽能加快检索速度,但若候选项太少,可能会降低召回率。
在本次对比中,Elasticsearch 在相同配置下展现出比 OpenSearch 更低的延迟和更高的吞吐量。
方法论
完整的配置、Terraform 脚本、Kubernetes 配置文件以及使用的特定 Rally track 均可在此仓库中的 openai_vector_bq 路径下找到。
与以往的基准测试一样,我们使用了一个 Kubernetes 集群,包含以下节点池配置:
-
1 个用于 Elasticsearch 9.0 的节点池,包含 3 台 e2-standard-32 机器(128GB 内存,32 核 CPU)
-
1 个用于 OpenSearch 2.19 的节点池,包含 3 台 e2-standard-32 机器(128GB 内存,32 核 CPU)
-
1 个用于 Rally 的节点池,包含 2 台 e2-standard-4 机器(16GB 内存,4 核 CPU)
我们分别搭建了一个 Elasticsearch 9.0 集群和一个 OpenSearch 2.19 集群。
Elasticsearch 和 OpenSearch 都在完全相同的配置下进行测试:我们使用经过一些修改的 openai_vector Rally track,该 track 使用了来自 NQ 数据集的 250 万个文档,并附加了使用 OpenAI 的 text-embedding-ada-002 模型生成的向量嵌入。
{"source-file": "open_ai_corpus-initial-indexing.json.bz2","document-count": 2580961,"compressed-bytes": 32076749416,"uncompressed-bytes": 90263571686
}
测试结果报告了在不同召回率水平(recall@10、recall@50 和 recall@100)下的延迟和吞吐量表现,搜索操作由 8 个并发客户端执行。我们使用了单个分片,且未设置副本。
我们运行了以下 k-n-rescore 的组合,例如 10-2000-2000,意思是:k 为 10,n 为 2000,rescore 也为 2000,表示从 2000 个候选中选出 top k(10 个)结果,并对这 2000 个候选进行重排序(相当于“过采样因子”为 1)。每次搜索运行了 10,000 次,其中前 1,000 次作为预热。
Recall@10
- 10-40-40
- 10-50-50
- 10-100-100
- 10-200-200
- 10-500-500
- 10-750-750
- 10-1000-1000
- 10-1500-1500
- 10-2000-2000
Recall@50
- 50-150-150
- 50-200-200
- 50-250-250
- 50-500-500
- 50-750-750
- 50-1000-1000
- 50-1200-1200
- 50-1500-1500
- 50-2000-2000
Recall@100
- 100-200-200
- 100-250-250
- 100-300-300
- 100-500-500
- 100-750-750
- 100-1000-1000
- 100-1200-1200
- 100-1500-1500
- 100-2000-2000
要复现该基准测试,rally-elasticsearch 和 rally-opensearch 的 Kubernetes 配置清单中,所有相关变量都已外部化到 ConfigMap 中,分别可在此处获取(ES)和此处获取(OS)。其中的 search_ops
参数可以自定义,用于测试任意组合的 k、n 和 rescore 值。
OpenSearch Rally 配置如下:
/k8s/rally-openai_vector-os-bq.yml
apiVersion: v1
kind: ConfigMap
metadata:name: rally-params-oslabels:app: rally-opensearch
data:user-tags.json: |{"product": "OpenSearch","product-version": "OpenSearch-2.19.0","product-label": "OpenSearch-2.19-faiss","benchmark-run": "19-feb-recall@100"}track-params.json: |{"mapping_type": "vectors-only-mapping-with-docid","standalone_search_clients": 8,"standalone_search_iterations": 5000,"ann_threshold": 0,"vector_mode": "on_disk","compression_level": "32x","vector_method_name": "hnsw","vector_method_engine": "faiss","search_ops": [[100, 200, 200],[100, 250, 250],[100, 300, 300],[100, 500, 500],[100, 750, 750],[100, 1000, 1000],[100, 1200, 1200],[100, 1500, 1500],[100, 2000, 2000]]}
OpenSearch 索引配置
ConfigMap 中的变量随后会用于索引配置,其中部分参数保持不变。在 OpenSearch 中,1-bit 量化通过将压缩等级设置为 “32x” 来配置。
index-vectors-only-mapping-with-docid-mapping.json
{"settings": {{% if preload_pagecache %}"index.store.preload": ["vec", "vex", "vem", "veq", "veqm", "veb", "vebm"],{% endif %}"index.number_of_shards": {{ number_of_shards | default(1) }},"index.number_of_replicas": {{ number_of_replicas | default(0) }},"index.knn": true,"index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}},"mappings": {"dynamic": false,"properties": {"docid": {"type": "keyword"},"emb": {"type": "knn_vector","dimension": 1536,"space_type": "innerproduct","data_type": "float","mode": {{ vector_mode | default("in_memory") | tojson }},"compression_level": {{ compression_level | default("32x") | tojson }},"method": {"name": {{ vector_method_name | default("hnsw") | tojson }},"engine": {{ vector_method_engine | default("faiss") | tojson }},"parameters": {"ef_construction": 100,"m": 16}}}}}
}
Elasticsearch Rally 配置
/k8s/rally-openai_vector-es-bq.yml
apiVersion: v1
kind: ConfigMap
metadata:name: rally-params-eslabels:app: rally-elasticsearch
data:user-tags.json: |{"product": "Elasticsearch","product-version": "Elasticsearch-9.0.0-ade01164","product-label": "Elasticsearch-9.0-BBQ","benchmark-run": "19-feb-recall@100"}track-params.json: |{"mapping_type": "vectors-only-mapping-with-docid","standalone_search_clients": 8,"standalone_search_iterations": 5000,"vector_index_type": "bbq_hnsw","search_ops": [[100, 200, 200],[100, 250, 250],[100, 300, 300],[100, 500, 500],[100, 750, 750],[100, 1000, 1000],[100, 1200, 1200],[100, 1500, 1500],[100, 2000, 2000]]}
Elasticsearch 索引配置
index-vectors-only-mapping-with-docid-mapping.json
{"settings": {{# non-serverless-index-settings-marker-start #}{%- if build_flavor != "serverless" or serverless_operator == true -%}{% if preload_pagecache %}"index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],{% endif %}"index.number_of_shards": {{ number_of_shards | default(1) }},"index.number_of_replicas": {{ number_of_replicas | default(0) }}{%- endif -%}{# non-serverless-index-settings-marker-end #}},"mappings": {"dynamic": false,"properties": {"docid": {"type": "keyword"},"emb": {"type": "dense_vector","element_type": "float","dims": 1536,"index": true,"similarity": "dot_product","index_options": {"type": {{ vector_index_type | default("bbq_hnsw") | tojson }},"ef_construction": 100,"m": 16}}}}
}
结果
有多种方式来解读结果。对于延迟和吞吐量,我们分别在每个召回率水平绘制了简化图表和详细图表。如果我们将“更高更好”作为每个指标的标准,那么很容易看到差异。然而,延迟是负向指标(更低更好),而吞吐量是正向指标。对于简化图表,我们使用了 (recall / latency) * 10000(简称 “速度”)和 recall * throughput,因此这两个指标意味着更高的速度和更高的吞吐量更好。接下来我们来看看。
Recall @ 10 - 简化图表
在该召回率水平下,Elasticsearch BBQ 的速度是 OpenSearch FAISS 的最多 5 倍(平均快 3.9 倍),且吞吐量平均是 OpenSearch FAISS 的 3.2 倍。
Recall @ 10 - 详细图表
task | latency.mean | throughput.mean | avg_recall | |
---|---|---|---|---|
Elasticsearch-9.0-BBQ | 10-100-100 | 11.70 | 513.58 | 0.89 |
Elasticsearch-9.0-BBQ | 10-1000-100 | 27.33 | 250.55 | 0.95 |
Elasticsearch-9.0-BBQ | 10-1500-1500 | 35.93 | 197.26 | 0.95 |
Elasticsearch-9.0-BBQ | 10-200-200 | 13.33 | 456.16 | 0.92 |
Elasticsearch-9.0-BBQ | 10-2000-2000 | 44.27 | 161.40 | 0.95 |
Elasticsearch-9.0-BBQ | 10-40-40 | 10.97 | 539.94 | 0.84 |
Elasticsearch-9.0-BBQ | 10-50-50 | 11.00 | 535.73 | 0.85 |
Elasticsearch-9.0-BBQ | 10-500-500 | 19.52 | 341.45 | 0.93 |
Elasticsearch-9.0-BBQ | 10-750-750 | 22.94 | 295.19 | 0.94 |
OpenSearch-2.19-faiss | 10-100-100 | 35.59 | 200.61 | 0.94 |
OpenSearch-2.19-faiss | 10-1000-1000 | 156.81 | 58.30 | 0.96 |
OpenSearch-2.19-faiss | 10-1500-1500 | 181.79 | 42.97 | 0.96 |
OpenSearch-2.19-faiss | 10-200-200 | 47.91 | 155.16 | 0.95 |
OpenSearch-2.19-faiss | 10-2000-2000 | 232.14 | 31.84 | 0.96 |
OpenSearch-2.19-faiss | 10-40-40 | 27.55 | 249.25 | 0.92 |
OpenSearch-2.19-faiss | 10-50-50 | 28.78 | 245.14 | 0.92 |
OpenSearch-2.19-faiss | 10-500-500 | 79.44 | 97.06 | 0.96 |
OpenSearch-2.19-faiss | 10-750-750 | 104.19 | 75.49 | 0.96 |
Recall @ 50 - 简化图表
在该召回率水平下,Elasticsearch BBQ 的速度是 OpenSearch FAISS 的最多 5 倍(平均快 4.2 倍),且吞吐量平均是 OpenSearch FAISS 的 3.9 倍。
Recall @ 50 - 详细结果
Task | Latency Mean | Throughput Mean | Avg Recall | |
---|---|---|---|---|
Elasticsearch-9.0-BBQ | 50-1000-1000 | 25.71 | 246.44 | 0.95 |
Elasticsearch-9.0-BBQ | 50-1200-1200 | 28.81 | 227.85 | 0.95 |
Elasticsearch-9.0-BBQ | 50-150-150 | 13.43 | 362.90 | 0.90 |
Elasticsearch-9.0-BBQ | 50-1500-1500 | 33.38 | 202.37 | 0.95 |
Elasticsearch-9.0-BBQ | 50-200-200 | 12.99 | 406.30 | 0.91 |
Elasticsearch-9.0-BBQ | 50-2000-2000 | 42.63 | 163.68 | 0.95 |
Elasticsearch-9.0-BBQ | 50-250-250 | 14.41 | 373.21 | 0.92 |
Elasticsearch-9.0-BBQ | 50-500-500 | 17.15 | 341.04 | 0.93 |
Elasticsearch-9.0-BBQ | 50-750-750 | 31.25 | 248.60 | 0.94 |
OpenSearch-2.19-faiss | 50-1000-1000 | 125.35 | 62.53 | 0.96 |
OpenSearch-2.19-faiss | 50-1200-1200 | 143.87 | 54.75 | 0.96 |
OpenSearch-2.19-faiss | 50-150-150 | 43.64 | 130.01 | 0.89 |
OpenSearch-2.19-faiss | 50-1500-1500 | 169.45 | 46.35 | 0.96 |
OpenSearch-2.19-faiss | 50-200-200 | 48.05 | 156.07 | 0.91 |
OpenSearch-2.19-faiss | 50-2000-2000 | 216.73 | 36.38 | 0.96 |
OpenSearch-2.19-faiss | 50-250-250 | 53.52 | 142.44 | 0.93 |
OpenSearch-2.19-faiss | 50-500-500 | 78.98 | 97.82 | 0.95 |
OpenSearch-2.19-faiss | 50-750-750 | 103.20 | 75.86 | 0.96 |
Recall @ 100
在该召回率水平下,Elasticsearch BBQ 的速度是 OpenSearch FAISS 的最多 5 倍(平均快 4.6 倍),且吞吐量平均是 OpenSearch FAISS 的 3.9 倍。
Recall @ 100 - 详细结果
task | latency.mean | throughput.mean | avg_recall | |
---|---|---|---|---|
Elasticsearch-9.0-BBQ | 100-1000-1000 | 27.82 | 243.22 | 0.95 |
Elasticsearch-9.0-BBQ | 100-1200-1200 | 31.14 | 224.04 | 0.95 |
Elasticsearch-9.0-BBQ | 100-1500-1500 | 35.98 | 193.99 | 0.95 |
Elasticsearch-9.0-BBQ | 100-200-200 | 14.18 | 403.86 | 0.88 |
Elasticsearch-9.0-BBQ | 100-2000-2000 | 45.36 | 159.88 | 0.95 |
Elasticsearch-9.0-BBQ | 100-250-250 | 14.77 | 433.06 | 0.90 |
Elasticsearch-9.0-BBQ | 100-300-300 | 14.61 | 375.54 | 0.91 |
Elasticsearch-9.0-BBQ | 100-500-500 | 18.88 | 340.37 | 0.93 |
Elasticsearch-9.0-BBQ | 100-750-750 | 23.59 | 285.79 | 0.94 |
OpenSearch-2.19-faiss | 100-1000-1000 | 142.90 | 58.48 | 0.95 |
OpenSearch-2.19-faiss | 100-1200-1200 | 153.03 | 51.04 | 0.95 |
OpenSearch-2.19-faiss | 100-1500-1500 | 181.79 | 43.20 | 0.96 |
OpenSearch-2.19-faiss | 100-200-200 | 50.94 | 131.62 | 0.83 |
OpenSearch-2.19-faiss | 100-2000-2000 | 232.53 | 33.67 | 0.96 |
OpenSearch-2.19-faiss | 100-250-250 | 57.08 | 131.23 | 0.87 |
OpenSearch-2.19-faiss | 100-300-300 | 62.76 | 120.10 | 0.89 |
OpenSearch-2.19-faiss | 100-500-500 | 84.36 | 91.54 | 0.93 |
OpenSearch-2.19-faiss | 100-750-750 | 111.33 | 69.95 | 0.94 |
BBQ 的改进
自首次发布以来,BBQ 取得了长足的进步。在 Elasticsearch 8.16 版本中,为了进行对比,我们包含了 8.16 的基准测试结果与当前版本的对比,可以看到自那时以来召回率和延迟已经得到了显著改善。
在 Elasticsearch 8.18 和 9.0 中,我们重新编写了向量量化的核心算法。因此,尽管 8.16 版本中的 BBQ 已经表现出色,但最新版本的性能更为强大。你可以在这里和这里阅读相关内容。简而言之,每个向量都通过优化的标量分位数进行单独量化。结果是,用户在向量搜索中可以享受到更高的准确性,同时不影响性能,从而使 Elasticsearch 的向量检索更强大。
结论
在 Elasticsearch BBQ 与 OpenSearch FAISS 的性能对比中,Elasticsearch 在向量搜索上显著优于 OpenSearch,查询速度最多快 5 倍,吞吐量在各个召回率水平上平均高出 3.9 倍。
主要发现包括:
-
Recall@10:Elasticsearch BBQ 最多比 OpenSearch FAISS 快 5 倍(平均快 3.9 倍),吞吐量平均是 OpenSearch FAISS 的 3.2 倍。
-
Recall@50:Elasticsearch BBQ 最多比 OpenSearch FAISS 快 5 倍(平均快 4.2 倍),吞吐量平均是 OpenSearch FAISS 的 3.9 倍。
-
Recall@100:Elasticsearch BBQ 最多比 OpenSearch FAISS 快 5 倍(平均快 4.6 倍),吞吐量平均是 OpenSearch FAISS 的 3.9 倍。
这些结果突出了 Elasticsearch BBQ 在高维向量搜索场景中的高效性和性能优势。Elasticsearch 8.16 中引入的更优二值量化(BBQ)技术提供了显著的内存压缩(约 95%),同时保持了高排名质量,使其成为大规模向量搜索应用的优选方案。
在 Elastic,我们不断创新,致力于提升 Apache Lucene 和 Elasticsearch,以提供最佳的向量数据库,适用于搜索和检索用例,包括 RAG(Retrieval Augmented Generation - 检索增强生成)。我们的最新进展显著提高了性能,使向量搜索比以往更快、更节省空间,基于 Lucene 10 的改进。本文正是这一创新的又一例证。
你可以通过这个自学搜索 AI 的实操教程亲自体验向量搜索。现在,你可以开始免费云试用,或者在本地机器上尝试 Elastic。
原文:Elasticsearch BBQ vs. OpenSearch FAISS: Vector search performance comparison - Elasticsearch Labs