您可以通过这个自定进度的动手学习来亲自体验 Search AI 的向量搜索功能。现在即可开始免费云试用或在本地机器上试用 Elastic。
在网络附加持久存储(大多数托管云部署实际使用的拓扑结构)上进行测试,Elasticsearch DiskBBQ 在可比召回率下,吞吐量比 Qdrant 高出 7 倍。这种差距在 0.93 到 0.97 的召回率水平上保持一致,并且随着召回率的增加而扩大。DiskBBQ 在搜索广度增加时仍能保持延迟几乎不变;而 Qdrant 的延迟随着 hnsw_ef 的增加而急剧上升,这主要是由于在重新评分过程中从磁盘随机读取原始向量所致。如果您在 Kubernetes 或托管云环境中运行向量搜索,那么这就是性能权衡的体现。

条形图比较了 Elasticsearch 9.4.1 和 Qdrant 1.18.1 在召回率 0.93、0.95、0.96 和 0.97 下每秒查询吞吐量,显示 Elasticsearch 在所有召回率下吞吐量大约高出 7 倍。
图 1:吞吐量与召回率。
向量搜索是大型语言模型(LLM)应用、检索增强生成(RAG)以及其他 AI 工作负载的关键基础。在此基准测试中,Elasticsearch 在相同的存储拓扑结构下,以可比召回率实现了比 Qdrant 高出 7 倍的吞吐量。即使网络附加持久存储仍在查询路径上,作为向量数据库的 Elasticsearch 也能提供强大的向量搜索性能。
这种差异反映了两个系统与磁盘交互的方式。Elasticsearch DiskBBQ 旨在当持久存储仍在查询路径上时保持向量搜索的高效性,它使用紧凑的量化表示,并限制了在搜索过程中对全精度向量的昂贵访问。在此设置中,Qdrant 依赖于基于图的搜索路径,并通过对存储在磁盘上的原始向量进行重新评分。在网络附加持久存储上,这种随机访问成本变得更为显著,这也是为什么性能差距随着召回率的增加而扩大的原因。因此,此基准测试特别关注网络附加持久存储,这是一种在托管云和 Kubernetes 环境中常见的部署模型。

折线图显示了 Elasticsearch 9.4.1 和 Qdrant 1.18.1 的召回率与平均延迟(毫秒)。Elasticsearch 在所有召回率下均保持 120 到 150 毫秒的低延迟,而 Qdrant 的延迟随着召回率的增加从 315 毫秒急剧上升到 900 毫秒。
图 2:召回率与平均延迟。
延迟曲线的关键模式不仅在于差距的大小,还在于其形状。Elasticsearch 的延迟随着召回率的增加保持相对平稳,这表明更高的召回率不需要大幅增加昂贵的存储活动。Qdrant 的延迟随着 hnsw_ef 的增加而急剧上升,这与更广泛的候选探索导致对磁盘上原始向量进行更多重新评分工作的情况一致。
下表显示了 Elasticsearch 和 Qdrant 的完整参数扫描结果。由于两个引擎暴露的向量搜索调优控件不同,因此结果使用每个引擎的完整参数键报告,而不是尝试在设置之间进行一对一的映射。
关于这些指标的一些说明:
Engine | ParamKey | Recall | Latency_Avg | Latency_P95 | Throughput |
|---|---|---|---|---|---|
qdrant | hnsw_ef=50, oversampling=1, size=100 | 0.8694 | 315.7849 | 503.4754 | 12.629 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=1 | 0.8789 | 135.0802 | 218.494 | 29.343 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=1.5 | 0.9123 | 127.8286 | 195.2318 | 31.1107 |
qdrant | hnsw_ef=100, oversampling=1, size=100 | 0.9287 | 895.9933 | 1213.0448 | 4.4493 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=2 | 0.9317 | 124.846 | 183.6314 | 31.8225 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=2.5 | 0.9444 | 123.517 | 180.4831 | 32.1883 |
qdrant | hnsw_ef=150, oversampling=1, size=100 | 0.9518 | 884.7236 | 1195.2603 | 4.5066 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=3 | 0.9532 | 123.276 | 183.8379 | 32.2364 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=3.5 | 0.9599 | 122.5559 | 184.2858 | 32.4469 |
qdrant | hnsw_ef=200, oversampling=1, size=100 | 0.964 | 883.2114 | 1188.6597 | 4.5143 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=4 | 0.965 | 122.7946 | 184.9058 | 32.3635 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=4.5 | 0.9689 | 122.7062 | 182.9559 | 32.3976 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=5 | 0.9722 | 122.5761 | 187.3536 | 32.4221 |
qdrant | hnsw_ef=256, oversampling=1, size=100 | 0.9722 | 881.9643 | 1185.4948 | 4.5192 |
elasticsearch | k=100, oversample=1, size=100, visit_percentage=5.5 | 0.9747 | 122.5609 | 184.5128 | 32.4176 |
每一行将扫描中测量到的最接近的 Elasticsearch 和 Qdrant 配置按其实现的召回率进行配对。
为了确保比较的公平性,加速比仅在实现相似召回率的配置之间进行计算。这避免了比较在准确性方面有很大差异的设置。
Recall band | Elasticsearch recall | Elasticsearch Latency_Avg | Elasticsearch throughput | Qdrant recall | Qdrant Latency_Avg | Qdrant throughput | Throughput speedup |
|---|---|---|---|---|---|---|---|
~0.87 | 0.8789 | 135.0802 | 29.343 | 0.8694 | 315.7849 | 12.629 | 2.32x |
~0.93 | 0.9317 | 124.846 | 31.8225 | 0.9287 | 895.9933 | 4.4493 | 7.15x |
~0.95 | 0.9532 | 123.276 | 32.2364 | 0.9518 | 884.7236 | 4.5066 | 7.15x |
~0.96 | 0.9599 | 122.5559 | 32.4469 | 0.964 | 883.2114 | 4.5143 | 7.19x |
~0.97 | 0.9722 | 122.5761 | 32.4221 | 0.9722 | 881.9643 | 4.5192 | 7.17x |
这种匹配召回率的视图最清晰地表达了底层系统的差异。在相似的召回率水平下,Elasticsearch 提供了更低的延迟和更高的吞吐量,并且随着召回率的提高,差距不断扩大。召回率-吞吐量模式之所以重要,是因为在此基准测试中,更高的召回率需要更广泛的搜索。DiskBBQ 以相对较少的额外成本吸收了这种增加,而 Qdrant 的图加重新评分路径则受到对持久存储上原始向量的随机访问的更多限制。
Jingra 是用于这些测试的基准测试工具,最初用 Python 编写,后来重建为 Java 项目。在这些测试中,Jingra 在与被测引擎相同的 Kubernetes 集群中的一个 pod 中运行。这有助于减少外部网络的可变性,并使测试环境在不同运行中保持一致。对于每次运行,Jingra 以固定的客户端并发度执行查询集,记录端到端客户端延迟和吞吐量,并根据预先计算的真实前 100 结果集计算召回率。
此基准测试特意在网络附加持久存储而不是本地 NVMe 上运行。对于已发布的结果,存储使用了 200 GiB GCP Hyperdisk Balanced 卷的基线性能分配,没有明确的 IOPS 或吞吐量配置。我们特意选择这种拓扑结构,因为它是一种相关的云部署模型,并且因为它使存储效率在查询路径上变得切实可见。
Qdrant 在本地 NVMe 上的表现通常更好,因此使用本地 NVMe 的部署应预期与此处所示的结果不同。此基准测试专门测试网络附加持久存储,因为这是一种常见的托管云部署模型,并且因为它使存储路径效率在端到端查询性能中可见。
由于 Elasticsearch 和 Qdrant 暴露了不同的查询参数来控制向量搜索行为,因此它们的调优设置之间没有清晰的一对一映射。我们不是直接比较等效的参数值,而是使用召回率作为主要的比较点。因此,下面的匹配比较将实现相似召回率的配置进行配对,而不是具有表面上相似参数值的配置。
召回率无法针对给定的参数设置提前知晓,因此我们对每个引擎的一系列搜索配置进行扫描,然后比较相似召回率水平下的结果。在已发布的结果中,两个引擎的过采样率(oversampling)都固定为 1,以便主要通过搜索广度而不是重新评分扩展来调整召回率。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"query": {
"knn": {
"field": "embedding",
"query_vector": "{{query_vector}}",
"k": "{{k}}",
"visit_percentage": "{{visit_percentage}}",
"rescore_vector": {
"oversample": "{{oversample}}"
}
}
},
"size": "{{size}}",
"_source": false
}
query_vector:用于相似性搜索的输入向量。Elasticsearch 将此向量与字段中存储的向量进行比较。k:要检索的最近邻居的数量。visit_percentage:控制在近似搜索阶段探索 DiskBBQ(Elasticsearch 的磁盘优化向量索引)的程度。较高的值通常会提高召回率但会增加延迟。oversample:控制传递到重新评分阶段的额外候选向量数量与 k 的相对值。较高的值可以提高召回率,但通常会增加额外成本。size:最终响应中返回的命中数。_source: false:禁用返回文档的 _source 字段,减少响应大小并避免基准测试期间的额外检索开销。示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"query": {
"knn": {
"field": "embedding",
"query_vector": [ -0.0095683, 0.0072035934, ... ],
"k": "100",
"visit_percentage": "3",
"rescore_vector": {
"oversample": "1"
}
}
},
"size": "100",
"_source": false
}
参数
1
2
3
4
5
6
7
8
9
10
11
recall@100:
- { size: 100, k: 100, visit_percentage: 1, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 1.5, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 2, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 2.5, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 3, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 3.5, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 4, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 4.5, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 5, oversample: 1 }
- { size: 100, k: 100, visit_percentage: 5.5, oversample: 1 }
我们保持 k = size = 100,使搜索请求与基准目标对齐:返回前 100 个结果。为了提高召回率,我们调整 visit_percentage 而不是增加最终结果计数,同时在所有运行中保持 oversample = 1 不变。
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"vector": "{{query_vector}}",
"limit": "{{size}}",
"with_payload": false,
"with_vector": false,
"params": {
"hnsw_ef": "{{hnsw_ef}}",
"quantization": {
"rescore": true,
"oversampling": "{{oversampling}}"
}
}
}
query_vector / vector:用于相似性搜索的输入向量。Qdrant 将此向量与集合中存储的向量进行比较。size / limit:响应中返回的最近邻居结果的数量。with_payload: false:禁用返回 payload 字段,减少响应大小并避免基准测试期间的额外检索开销。with_vector: false:禁用在响应中返回存储的向量,再次减少响应大小并将基准测试重点放在搜索性能上。hnsw_ef:控制 HNSW 搜索期间探索的候选者数量。较高的值通常会提高召回率但会增加延迟。与 Elasticsearch 中的 visit_percentage 类似,它影响搜索广度,但这两个控件是引擎特定的,不能直接等同。quantization.rescore: true:在量化搜索后,使用原始向量对候选集进行重新评分。oversampling:控制在重新评分期间考虑的额外候选者数量与最终结果计数的相对值。较高的值可以提高召回率,但通常会增加额外成本。示例
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"vector": [ -0.0095683, 0.0072035934, ... ],
"limit": "100",
"with_payload": false,
"with_vector": false,
"params": {
"hnsw_ef": "150",
"quantization": {
"rescore": true,
"oversampling": "1"
}
}
}
参数
1
2
3
4
5
6
recall@100:
- { size: 100, hnsw_ef: 50, oversampling: 1 }
- { size: 100, hnsw_ef: 100, oversampling: 1 }
- { size: 100, hnsw_ef: 150, oversampling: 1 }
- { size: 100, hnsw_ef: 200, oversampling: 1 }
- { size: 100, hnsw_ef: 256, oversampling: 1 }
我们保持 size = 100,以便每个请求都与评估目标对齐,在本例中是检索前 100 个结果。然后通过扫描 hnsw_ef 来调整召回率,这控制了搜索期间探索的候选者数量。较高的 hnsw_ef 值通常会提高召回率,但也会增加延迟并降低吞吐量。我们在所有运行中保持 oversampling = 1 不变,以便主要的调优变量是搜索广度而不是重新评分扩展。
我们在 GCP 上使用三个 n4-standard-8 节点运行了基准测试,每个 pod 分配 7 个 vCPU 和 26 GB RAM,并使用 200 GiB GCP Hyperdisk Balanced 卷的基线性能分配。语料库包含 2100 万个向量(更多详细信息和下载链接请参阅下面的数据集部分),大约占 60.1 GiB 的原始浮点向量数据。使用 2 位量化后,向量负载降至大约 3.8 到 4.0 GB。然而,一旦包含图和其他索引结构,完整的索引占用空间会大得多。这意味着工作负载仍然对网络附加存储性能有显著影响,特别是因为在重新评分期间仍然需要从磁盘读取精确的向量值。
我们特意选择这种节点大小,旨在将基准测试保持在网络附加持久存储仍在查询路径上的状态,而不是让完整的活动数据集舒适地驻留在内存中。因此,每个系统都使用我们在此帖子中描述的调优范围内为该工作负载确定的最佳性能设置进行配置。在 Elasticsearch 中,这意味着使用 bbq_disk。在 Qdrant 中,原始向量存储在磁盘上,而用于近似搜索的 2 位量化表示则通过 always_ram: true 保留在 RAM 中。由于两个系统暴露了不同的搜索策略和调优控件,我们通过匹配召回率而不是尝试一对一映射参数来比较它们。
Elasticsearch 配置为使用 DiskBBQ,这是其针对近似最近邻向量搜索的磁盘优化方法,并采用 2 位量化。DiskBBQ 使用激进的量化来保持可搜索索引的紧凑性,然后使用原始向量进行重新评分以保持准确性。这有助于在保持磁盘搜索效率的同时维持高召回率。
bbq_disk 是 Elasticsearch Enterprise 的一项功能。我们在此处使用它,因为此基准测试的目标是比较每个引擎中可用于此工作负载的最强磁盘导向型向量搜索配置,而不是许可层级或默认功能。
我们没有将 bbq_hnsw 纳入此比较,因为该基准测试专门设计用于评估磁盘敏感型工作负载下的磁盘导向型向量搜索。
这种存储拓扑结构很重要,因为 Qdrant 的重新评分步骤在每次查询时都会从磁盘随机读取原始 float32 向量。在本地 NVMe 上,这些读取速度要快得多,Qdrant 的性能也相应更好。在网络附加持久存储上,结果与随机读取重新评分路径成为更重要的瓶颈的情况一致。Qdrant 的延迟随着 hnsw_ef 的增加而急剧上升,而 Elasticsearch 在相同的召回率进展中保持相对平稳。
我们选择 2 位量化是因为 Qdrant 无法通过 1 位二值量化达到目标召回率范围。由于两个系统暴露了不同的磁盘导向型向量搜索策略,我们根据其当前功能集将每个系统调优到最强的配置。
两个系统都配置了三个分片分布在三个节点上,并且集群中每个分片共有两个副本。在 Elasticsearch 中,number_of_shards: 3 和 number_of_replicas: 1 意味着一个主副本加一个副本,总共两个副本。在 Qdrant 中,shard_number: 3 和 replication_factor: 2 也意味着总共两个副本,因为 Qdrant 的复制因子指的是总副本数量而不是额外副本的数量。因此,尽管字段名称不同,但两个系统中有效的复制级别是相同的。
Setting | Elasticsearch | Qdrant |
|---|---|---|
Shards | number_of_shards: 3 | shard_number: 3 |
Copies | number_of_replicas: 1 (1 primary + 1 replica = 2 total) | replication_factor: 2 (2 total) |
Elasticsearch 映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"mappings": {
"properties": {
"embedding": {
"type": "dense_vector",
"element_type": "float",
"dims": 768,
"index": true,
"similarity": "cosine",
"index_options": {
"type": "bbq_disk",
"bits": 2
}
}
}
},
"settings": {
"number_of_shards": "3",
"number_of_replicas": "1"
}
}
Qdrant 映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"vectors": {
"size": 768,
"distance": "Cosine",
"on_disk": true
},
"shard_number": 3,
"replication_factor": 2,
"hnsw_config": {
"m": 16,
"ef_construct": 256
},
"quantization_config": {
"turbo": {
"bits": "bits2",
"always_ram": true
}
}
}

架构图显示了 GCP 上的基准测试集群设置。两个 Kubernetes 集群并排:左侧包含三个 Elasticsearch 节点,位于 ES Service 后,Jingra 作为基准测试客户端。右侧镜像此设置,包含三个 Qdrant 节点,位于 QD Service 后,同样由 Jingra 驱动。
图 1:集群设置图。
在此基准测试中,我们使用了来自 Hugging Face 的 kenhktsui/wiki_dpr_e5 数据集,这是一个专为密集向量搜索设计的大规模维基百科段落检索数据集。该语料库包含 2100 万个嵌入式段落,每个段落表示为一个 768 维的 float32 向量,即每个向量 3,072 字节。这对应于大约 60.1 GiB 的原始向量数据,尚未考虑源数据集中额外的字段和文件格式开销。因此,可下载的 data.parquet 文件更大,为 85.2 GB。
我们选择此数据集是因为它反映了 LLM、RAG 和检索系统中常见的生产模式:搜索大型语义嵌入文本语料库,同时平衡召回率、延迟和吞吐量。拥有 2100 万个向量和大约 60 GiB 的原始向量数据,它足够大,使得基于磁盘的向量搜索成为一个相关的评估操作模式。
两个引擎都使用了 2 位量化,将每个向量从 3,072 字节减少到 192 字节,减少了 16 倍,使量化向量语料库达到约 4 GB。在 Qdrant 中,该量化表示保留在 RAM 中进行搜索,而原始向量则保留在磁盘上。即便如此,工作负载仍然对网络附加存储性能有显著影响,因为重新评分仍然需要访问磁盘上的原始向量。
您可以从以下链接下载数据集和查询文件:
在此基准测试中,我们使用了 Jingra v0.2.3 以及 es-9.4-vs-qd-1.18-vector-search 中描述的配置。Jingra 处理了 Elasticsearch 和 Qdrant 的数据加载、查询执行、参数扫描和指标收集,使基准测试具有可重复性且更易于比较。
要重现此实验,您需要已发布的数据集、查询集、引擎配置和可比较的集群硬件。具备这些条件后,Jingra 可以重新运行基准测试,并生成与本文中所示相似的召回率、延迟和吞吐量测量结果。
在此基准测试中,在可比召回率水平下,Elasticsearch DiskBBQ 始终比 Qdrant 提供更快的向量搜索速度,在测试的召回率范围内具有更高的吞吐量和更低的延迟。这些结果尤其值得关注,因为比较是在网络附加持久存储上进行的,在这种情况下,高效的存储感知型向量搜索变得至关重要。作为向量数据库的 Elasticsearch 允许组织在较慢的持久存储上以更低的延迟和更高的吞吐量实现高召回率。
同样重要的是,此基准测试强调了在匹配召回率下而不是通过名义参数设置来比较引擎的价值。Elasticsearch 和 Qdrant 暴露了不同的控制机制,因此最公平的比较不是参数对参数,而是结果对结果。在本文测试的召回率范围内,Elasticsearch 在延迟和吞吐量方面均保持了明显的优势。
如果您想亲自重现此实验,我们正在发布此基准测试中使用的数据集和查询集,以便其他人可以验证结果并在此基础上进行扩展。
进一步阅读: