我们在包含 2000 万篇文档的语料库上进行的基准测试表明,Elasticsearch 在过滤向量搜索方面比 OpenSearch 的吞吐量高出 8 倍,并且在我们测试的所有配置中都实现了更高的 Recall@100。上下文工程不仅仅依赖于快速的向量检索。随着工作流程的迭代,团队还需要强大的相关性控制(例如混合搜索和过滤)、操作简便性和可预测的性能。但是,由于代理通常在每个请求中多次运行检索、推理、再检索的循环,检索延迟会成为一个倍增器,因此这方面的改进将直接转化为更好的端到端响应速度和更低的成本。

图 1:吞吐量。
对于上下文工程而言,检索并非一次性步骤。代理和应用程序会反复运行循环,例如检索→推理→检索,以优化查询、验证事实、构建相关的上下文并完成任务。这种模式在代理工作流和迭代检索增强生成(RAG)中很常见。由于每个用户请求都可能多次调用检索,因此会增加响应延迟和/或基础设施成本。

图 1:上下文工程通过重复检索和整理,将大型上下文池(文档、内存、工具、聊天记录)转换为有限的大型语言模型 (LLM) 上下文窗口。
上下文工程的优化实施是一项新兴技术。迭代次数因工作流程而异。这些基准测试结果中最根本的关键概念是上下文工程具有方向性:迭代检索会成倍增加延迟。
想象一下,一位购物助理回答这个问题:“我需要一个价格低于 60 美元的随身背包,可以装下 15 英寸的笔记本电脑,防水,并且周五之前能送到。”
在生产环境中,助手很少只发出一次向量查询就停止。它会运行一个检索循环来构建正确的上下文,并且每一步通常都会受到诸如可用性、地区、发货承诺、品牌规则和政策资格等筛选条件的限制。
第一步:理解意图并将其转化为约束条件。
代理将请求转换为结构化过滤器和语义查询,例如:
步骤 2:检索候选对象,然后进行筛选。
它经常使用不同的方法重复检索,以避免错过好的匹配项:
每个查询都使用相同的资格筛选器,因为检索不相关或不可用的项目会浪费上下文信息。
步骤 3:展开确认细节并降低风险。
然后,代理再次检索以验证影响最终答案的关键属性:
这是多步骤上下文工程:检索、推理、检索、组装。
每个用户会话中,这些交互可能涉及数十次筛选检索调用。这使得每次调用的延迟直接倍增端到端响应时间,而低召回率则迫使代理进行额外的重试,或导致其错过符合条件的项目,从而降低答案质量。
要点:在上下文相关的系统中,过滤近似最近邻(ANN)并非单次查找,而是一项在约束条件下重复进行的操作。因此,即使大型语言模型(LLM)是最显眼的组件,向量搜索性能也会立即体现在延迟、吞吐量和成本上。
在图 2 中,每个点代表一个测试配置。最佳结果出现在左上角附近,这意味着更高的召回率和更低的延迟。Elasticsearch 的结果始终比 OpenSearch 更靠近左上角,表明在相同的工作负载设置下,Elasticsearch 的速度和准确性都更胜一筹。

图 2:回忆率与平均延迟的关系,重新评分为 1。
s_n_r_value:简写形式size_numCandidates_rescoreOversample(在这些测试中,k 和 numCandidates 均等于 numCandidates),例如,100_500_1表示 size=100,numCandidates=500 和 k=500,rescore oversample=1引擎 | `s_n_r_value` | 记起 | 平均延迟(毫秒) | 吞吐量 | 记起 % | 延迟 Xs | 吞吐量 Xs |
|---|---|---|---|---|---|---|---|
Elasticsearch | 100_250_1 | 0.7704 | 25 | 534.75 | 9.70% | 2.28 | 1.91 |
OpenSearch | 100_250_1 | 0.7023 | 57.08 | 279.58 | |||
Elasticsearch | 100_500_1 | 0.8577 | 25.42 | 524.14 | 7.20% | 2.4 | 2 |
OpenSearch | 100_500_1 | 0.8001 | 60.9 | 262.12 | |||
Elasticsearch | 100_750_1 | 0.8947 | 29.67 | 528.09 | 5.72% | 2.25 | 2.21 |
OpenSearch | 100_750_1 | 0.8463 | 66.76 | 239.11 | |||
Elasticsearch | 100_1000_1 | 0.9156 | 29.65 | 534.5 | 4.66% | 2.46 | 2.44 |
OpenSearch | 100_1000_1 | 0.8748 | 72.88 | 219.01 | |||
Elasticsearch | 100_1500_1 | 0.9386 | 31.84 | 497.3 | 3.38% | 2.71 | 2.68 |
OpenSearch | 100_1500_1 | 0.9079 | 86.16 | 185.4 | |||
Elasticsearch | 100_2000_1 | 0.9507 | 34.69 | 457.2 | 2.57% | 2.98 | 2.96 |
OpenSearch | 100_2000_1 | 0.9269 | 103.36 | 154.55 | |||
Elasticsearch | 100_2500_1 | 0.9582 | 37.9 | 418.43 | 1.99% | 3.28 | 3.26 |
OpenSearch | 100_2500_1 | 0.9395 | 124.29 | 128.53 | |||
Elasticsearch | 100_3000_1 | 0.9636 | 41.86 | 379.4 | 1.62% | 3.46 | 3.44 |
OpenSearch | 100_3000_1 | 0.9482 | 144.67 | 110.34 | |||
Elasticsearch | 100_4000_1 | 0.9705 | 50.28 | 316.21 | 1.06% | 3.87 | 3.85 |
OpenSearch | 100_4000_1 | 0.9603 | 194.36 | 82.22 | |||
Elasticsearch | 100_5000_1 | 0.9749 | 58.77 | 270.91 | 0.73% | 4.43 | 4.41 |
OpenSearch | 100_5000_1 | 0.9678 | 260.33 | 61.38 | |||
Elasticsearch | 100_6000_1 | 0.9781 | 66.75 | 238.59 | 0.52% | 4.91 | 4.89 |
OpenSearch | 100_6000_1 | 0.973 | 327.44 | 48.81 | |||
Elasticsearch | 100_7000_1 | 0.9804 | 74.64 | 213.49 | 0.38% | 5.28 | 5.27 |
OpenSearch | 100_7000_1 | 0.9767 | 394.24 | 40.53 | |||
Elasticsearch | 100_8000_1 | 0.9823 | 82.28 | 193.59 | 0.27% | 6.86 | 6.83 |
OpenSearch | 100_8000_1 | 0.9797 | 564.14 | 28.33 | |||
Elasticsearch | 100_9000_1 | 0.9837 | 90.08 | 176.96 | 0.16% | 7.63 | 7.61 |
OpenSearch | 100_9000_1 | 0.9821 | 687.25 | 23.25 | |||
Elasticsearch | 100_10000_1 | 0.9848 | 97.64 | 163.31 | 0.08% | 8.38 | 8.36 |
OpenSearch | 100_10000_1 | 0.984 | 818.64 | 19.53 |
例如,在100_9000_1OpenSearch 上,每次检索平均耗时 687 毫秒,而 Elasticsearch 每次检索平均耗时 90 毫秒;在一个 10 步检索循环中,大约需要额外等待 10 x (687 - 90) = 6 秒。
查看完整结果。
我们使用 Python 发送查询并跟踪响应时间和其它统计数据,向搜索引擎发送了以下查询。请注意,任何向量搜索引擎的性能都取决于其核心参数的调优方式:考虑多少候选答案、重新评分的频率以及返回多少上下文信息。这些设置直接影响召回率(找到正确答案的可能性)和延迟(获得结果的速度)。
在我们的基准测试中,我们使用了与代理检索循环中通常调整的相同的候选列表、重评分和结果大小设置,并测量了 Elasticsearch 在此工作负载下的性能。然后,我们使用相同的设置运行 OpenSearch 作为参考。
OpenSearch
GET <INDEX_NAME>/_search
{
"query": {
"knn": {
"<DENSE_VECTOR_FIELD_NAME>": {
"vector": [...],
"k": <NUMBER_OF_CANDIDATES>,
"method_parameters": {
"ef_search": <NUMBER_OF_CANDIDATES>
},
"rescore": {
"oversample_factor": <OVERSAMPLE>
},
"filter": {
<SOME_FILTER>
}
}
}
},
"size": <RESULT_SIZE>,
"_source": {
"excludes": [
"<DENSE_VECTOR_FIELD_NAME>"
]
}
}"size": <RESULT_SIZE>返回给客户端的命中次数。在此基准测试中,结果集大小为 100,以计算 Recall@100。"k": <NUMBER_OF_CANDIDATES>:最近邻候选对象的数量。"ef_search": <NUMBER_OF_CANDIDATES>要检查的向量数量。"oversample_factor": <OVERSAMPLE>:在重新评分之前检索到多少个候选向量。Elasticsearch
GET <INDEX_NAME>/_search
{
"query": {
"knn": {
"field": "<DENSE_VECTOR_FIELD_NAME>",
"query_vector": [...],
"k": <NUMBER_OF_CANDIDATES>,
"num_candidates": <NUMBER_OF_CANDIDATES>,
"rescore_vector": {
"oversample": <OVERSAMPLE>
},
"filter": {
<SOME_FILTER>
}
}
},
"size": <RESULT_SIZE>,
"_source": {
"excludes": [
"<DENSE_VECTOR_FIELD_NAME>"
]
}
}"size": <RESULT_SIZE>返回给客户端的命中次数。在此基准测试中,结果集大小为 100,以计算 Recall@100。"k": <NUMBER_OF_CANDIDATES>:从每个分片返回的最近邻居的数量。"num_candidates": <NUMBER_OF_CANDIDATES>:在进行搜索时,每个分片要考虑的最近邻候选数knn。"oversample": <OVERSAMPLE>:在重新评分之前检索到多少个候选向量。例子
Knn查询语句100_500_1如下:
OpenSearch
GET search_catalog_128/_search
{
"query": {
"knn": {
"search_catalog_embedding": {
"vector": [...],
"k": 500,
"method_parameters": {
"ef_search": 500
},
"rescore": {
"oversample_factor": 1
},
"filter": {
"term": {
"valid": true
}
}
}
}
},
"size": 100,
"_source": {
"excludes": [
"search_catalog_embedding"
]
}
}Elasticsearch
GET search_catalog_128/_search
{
"query": {
"knn": {
"field": "search_catalog_embedding",
"query_vector": [...],
"k": 500,
"num_candidates": 500,
"rescore_vector": {
"oversample": 1
},
"filter": {
"term": {
"valid": true
}
}
}
},
"size": 100,
"_source": {
"excludes": [
"search_catalog_embedding"
]
}
}完整的配置,以及 Terraform 脚本、Kubernetes 清单和基准测试代码,都可以在此存储库的es-9.3-vs-os-3.5-vector-search文件夹中找到。
我们在六台 e2-standard-16 云服务器上进行了测试,每台服务器配备 16 个 vCPU 和 64 GB 内存。在每台服务器上,我们为运行搜索引擎节点的每个 Kubernetes pod 分配了 15 个 vCPU 和 56 GB 内存,并为 JVM 堆保留了 28 GB 内存。
集群运行的是 Elasticsearch 9.3.0 和 OpenSearch 3.5.0(Lucene 10.3.2)。由于两个系统在此基准测试中使用了相同的 Lucene 版本,因此我们观察到的吞吐量和延迟差异不能仅仅归因于 Lucene,而是反映了每个引擎在集成和执行过滤后的 k 近邻 (kNN) 检索和重评分方面存在的差异。我们使用了一个包含三个主分片和一个副本的索引(总共 6 个分片,每个节点 1 个)。
我们还使用同一区域内的另一台服务器来运行基准测试客户端并收集计时统计数据。

图 2:集群设置示意图。
为了进行这项基准测试,我们使用了一个包含 2000 万份文档的大规模电子商务风格目录嵌入数据集,旨在反映现实世界大规模过滤向量检索。
每个文档代表一个目录项,包含:
我们选择这个数据集是因为它捕捉到了我们在生产环境中遇到的智能体和 RAG 类系统的核心性能挑战:仅靠向量相似度是不够的,检索经常受到过滤器的限制,系统必须在这些限制下保持高召回率和低延迟。与规模较小的问答类数据集相比,包含 2000 万篇文档的语料库也能更好地反映过滤式人工神经网络系统在实践中面临的规模和候选词压力。
在现代人工智能架构中,尤其是在围绕上下文工程构建的架构中,向量搜索速度并非无关紧要的实现细节,而是一个关键的倍增器。当智能体和工作流迭代执行“检索→推理→检索”流程时,检索性能直接影响端到端延迟、吞吐量以及输入模型的上下文质量。
在我们的基准测试中,当正确性取决于检索到正确的文档(而不仅仅是相似的向量)时,Elasticsearch 始终比 OpenSearch 提供更高的召回率和更低的延迟。在受控数据集上,这种差异显而易见;在生产环境中,这些优势会随着大量检索调用而累积,从而提高响应速度、增加容量余量并降低基础设施成本。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。