并发分段搜索
使用并发分段搜索在查询阶段并行搜索分段。并发分段搜索能够提高搜索延迟的情况包括:
- 发送长时间运行的请求时,例如,包含聚合或大范围的请求
- 作为将分段强制合并为单个分段以提高性能的替代方案
背景
在 OpenSearch 中,每个搜索请求都遵循分散-收集(scatter-gather)协议。协调节点接收搜索请求,评估需要哪些分片来处理此请求,并向每个分片发送分片级搜索请求。每个接收请求的分片使用 Lucene 本地执行请求并返回结果。协调节点合并从所有分片收到的响应,并将搜索响应返回给客户端。如果客户端请求响应中的任何文档字段或整个文档,协调节点可以选择在将最终结果返回给客户端之前执行一个获取(fetch)阶段。
并发搜索分段
如果没有并发分段搜索,Lucene 会在查询阶段在每个分片的所有分段上顺序执行请求。然后,查询阶段会收集搜索请求的命中结果。通过并发分段搜索,每个分片级请求将在查询阶段并行搜索分段。对于每个分片,分段被分成多个切片。每个切片是可以在单独线程上并行执行的工作单元,因此切片计数决定了分片级请求的最大并行度。一旦所有切片完成工作,Lucene 会对这些切片执行归约操作,将它们合并并为该分片级请求创建最终结果。切片使用新的 index_searcher
线程池执行,这与处理分片级请求的 search
线程池不同。
在索引或集群级别启用并发分段搜索
从 OpenSearch 3.0 版本开始,并发分段搜索在集群级别默认启用。默认的并发分段搜索模式是 auto
。升级后,聚合工作负载可能会遇到 CPU 利用率增加的情况。我们建议监控集群的资源使用情况,并根据需要调整基础设施容量以保持最佳性能。
要配置集群上的并发分段搜索,请使用 search.concurrent_segment_search.mode
设置。较旧的 search.concurrent_segment_search.enabled
设置将在未来版本发布中弃用,取而代之的是新设置。
您可以在两个级别启用并发分段搜索:
- 集群级别
- 索引级别
索引级别设置优先于集群级别设置。因此,如果集群设置启用但索引设置禁用,则该索引的并发分段搜索将被禁用。因此,除非明确设置,否则无论设置的默认值如何,都不会评估索引级别设置。您可以通过调用 索引设置 API 并省略 ?include_defaults
查询参数来检索索引级别设置的当前值。
集群级别和索引级别的 search.concurrent_segment_search.mode
设置均接受以下值:
-
auto
(默认):在此模式下,OpenSearch 将使用可插拔的并发搜索决策器根据查询评估和请求中是否存在聚合来决定是使用并发路径还是顺序路径来处理搜索请求。默认情况下,如果没有插件配置任何决策器,则使用并发搜索的决定将基于请求中是否存在聚合。有关可插拔决策器语义的更多信息,请参阅可插拔并发搜索决策器。 -
all
:对所有搜索请求启用并发分段搜索。这相当于将search.concurrent_segment_search.enabled
设置为true
。 -
none
:禁用所有搜索请求的并发分段搜索,从而关闭该功能。这相当于将search.concurrent_segment_search.enabled
设置为false
。
要为集群中所有索引的所有搜索请求启用并发分段搜索,请发送以下请求:
PUT _cluster/settings
{
"persistent":{
"search.concurrent_segment_search.mode": "all"
}
}
要在特定索引上为所有搜索请求启用并发分段搜索,请在端点中指定索引名称:
PUT <index-name>/_settings
{
"index.search.concurrent_segment_search.mode": "all"
}
您可以继续使用现有的 search.concurrent_segment_search.enabled
设置来为集群中的所有索引启用并发分段搜索,如下所示:
PUT _cluster/settings
{
"persistent":{
"search.concurrent_segment_search.enabled": true
}
}
要在特定索引上启用并发分段搜索,请在端点中指定索引名称:
PUT <index-name>/_settings
{
"index.search.concurrent_segment_search.enabled": true
}
在评估集群上是否启用并发分段搜索时,search.concurrent_segment_search.mode
设置优先于 search.concurrent_segment_search.enabled
设置。如果未明确设置 search.concurrent_segment_search.mode
,则将评估 search.concurrent_segment_search.enabled
设置以确定是否启用并发分段搜索。
从指定旧版 search.concurrent_segment_search.enabled
设置的早期版本升级集群时,此设置将继续生效。但是,一旦设置了 search.concurrent_segment_search.mode
,它将覆盖以前的设置,根据指定的模式启用或禁用并发搜索。我们建议在配置 search.concurrent_segment_search.mode
后将集群上的 search.concurrent_segment_search.enabled
设置为 null
PUT _cluster/settings
{
"persistent":{
"search.concurrent_segment_search.enabled": null
}
}
要禁用特定索引的旧设置,请在端点中指定索引名称:
PUT <index-name>/_settings
{
"index.search.concurrent_segment_search.enabled": null
}
切片机制
您可以选择两种可用的机制之一将分段分配给切片:默认的最大切片计数机制或Lucene 机制。
最大切片计数机制
最大切片计数机制是一种切片机制,它使用可动态配置的最大切片数量,并以轮询方式在切片之间划分分段。当顶级分片请求过多,并且您希望限制每个请求的切片数量以减少切片之间的竞争时,这非常有用。
从 OpenSearch 3.0 版开始,并发分段搜索默认使用最大切片计数机制。最大切片计数在集群启动时使用公式 Math.max(1, Math.min(Runtime.getRuntime().availableProcessors() / 2, 4))
计算。您可以通过在集群级别或索引级别显式设置 max_slice_count
参数来覆盖此值。有关更新 max_slice_count
的更多信息,请参阅设置切片机制。要恢复为默认计算值,请将 max_slice_count
设置为 null
。
Lucene 机制
Lucene 机制是最大切片计数机制的替代方案。默认情况下,Lucene 为分片中的每个切片分配最多 25 万个文档或 5 个段(以先达到者为准)。例如,考虑一个包含 11 个段的分片。前 5 个段各有 25 万个文档,后 6 个段各有 2 万个文档。前 5 个段将被分配到各自的切片中,因为每个段都包含切片允许的最大文档数。接着,接下来的 5 个段将全部被分配到另一个单独的切片中,因为达到了切片允许的最大段数。第 11 个段将被分配到单独的切片中。
设置切片机制
您可以通过更新 search.concurrent.max_slice_count
设置,在集群级别或索引级别设置切片机制。
集群级别和索引级别的 search.concurrent.max_slice_count
设置可以采用以下有效值
- 正整数:使用最大目标切片计数机制。通常,2 到 8 之间的值应该足够。
0
:使用 Lucene 机制。
要配置集群中所有索引的切片计数,请使用以下动态集群设置
PUT _cluster/settings
{
"persistent":{
"search.concurrent.max_slice_count": 2
}
}
要配置特定索引的切片计数,请在端点中指定索引名称
PUT <index-name>/_settings
{
"index.search.concurrent.max_slice_count": 2
}
一般准则
并发分段搜索有助于提高搜索请求的性能,但会消耗更多资源,例如 CPU 或 JVM 堆。测试您的工作负载以了解集群是否已正确配置并发分段搜索的大小非常重要。我们建议遵循以下并发分段搜索准则
- 从切片计数 2 开始,衡量您的工作负载性能。如果资源利用率超过建议值,请考虑扩展您的集群。根据我们的测试,我们观察到如果您的工作负载已经消耗了超过 50% 的 CPU 资源,那么您需要为并发分段搜索扩展您的集群。
- 如果您的切片计数为 2,并且集群中仍有可用资源,那么您可以将切片计数增加到更高的数字,例如 4 或 6,同时监控集群中的搜索延迟和资源利用率。
- 当许多客户端并行发送搜索请求时,较低的切片计数通常效果更好。这反映在 CPU 利用率上,因为更多的客户端会导致每秒查询次数增加,从而转化为更高的资源使用率。
升级到 OpenSearch 3.0 时,请注意包含聚合的工作负载可能会遇到更高的 CPU 利用率,因为并发搜索在 auto
模式下默认启用。如果您的 OpenSearch 2.x 集群在运行聚合工作负载时 CPU 利用率超过 25%,请在升级前考虑以下选项
- 计划扩展集群资源以适应增加的 CPU 需求。
- 如果您的用例无法进行扩展,请准备禁用并发搜索。
限制
以下聚合不支持并发搜索模型。如果搜索请求包含这些聚合中的一个,即使在集群级别或索引级别启用了并发分段搜索,该请求也将使用非并发路径执行。
- join 字段上的父聚合。有关更多信息,请参阅此 GitHub 问题。
sampler
和diversified_sampler
聚合。有关更多信息,请参阅此 GitHub 问题。
其他注意事项
以下部分提供了并发分段搜索的其他注意事项。
terminate_after
搜索参数
terminate_after
搜索参数 用于在收集到指定数量的匹配文档后终止搜索请求。如果您在请求中包含 terminate_after
参数,并发分段搜索将被禁用,并且该请求将以非并发方式运行。
通常,查询与较小的 terminate_after
值一起使用,因此完成速度很快,因为搜索是在缩减的数据集上执行的。因此,在这种情况下,并发搜索可能无法进一步提高性能。此外,当 terminate_after
与其他搜索请求参数(例如 track_total_hits
或 size
)一起使用时,会增加复杂性并改变预期的查询行为。对于包含 terminate_after
的搜索请求回退到非并发路径,可以确保并发请求和非并发请求之间结果的一致性。
排序
根据段的数据布局,排序优化功能可以根据最小值和最大值以及先前收集的值来修剪整个段。如果最高值存在于前几个段中并且所有其他段都被修剪,则使用并发分段搜索进行排序时,查询延迟可能会增加。相反,如果最后几个段包含最高值,则并发分段搜索可能会提高延迟。
Terms 聚合
非并发搜索会计算文档计数错误并将其作为 doc_count_error_upper_bound
响应参数返回。在并发分段搜索期间,shard_size
参数应用于分段切片级别。因此,并发搜索可能会引入额外的文档计数错误。
有关 shard_size
如何同时影响 doc_count_error_upper_bound
和收集到的桶的更多信息,请参阅此 GitHub 问题。
开发者信息
以下部分为开发者提供了额外信息。
AggregatorFactory 更改
由于实现细节,并非所有聚合器类型都支持并发分段搜索。为了适应这一点,我们在 AggregatorFactory
类中引入了 supportsConcurrentSegmentSearch()
方法,用于指示给定聚合类型是否支持并发分段搜索。默认情况下,此方法返回 false
。任何需要支持并发分段搜索的聚合器都必须在其自己的工厂实现中覆盖此方法。
为确保自定义的基于插件的 Aggregator
实现能在并发搜索路径下正常工作,插件开发者可以通过启用并发搜索来验证其实现,然后更新插件以覆盖 supportsConcurrentSegmentSearch()
方法以返回 true
。
可插拔的并发搜索决策器:ConcurrentSearchRequestDecider
2.17 版本引入
插件开发者可以通过扩展 ConcurrentSearchRequestDecider
并通过 SearchPlugin#getConcurrentSearchRequestFactories()
注册其工厂来定制 auto
模式下的并发搜索决策。只有当请求不属于限制和其他注意事项部分中列出的任何类别时,决策器才会被评估。有关决策器实现的更多信息,请参阅相应的 GitHub 问题。搜索请求使用 QueryBuilderVisitor
进行解析,它会为搜索请求中 QueryBuilder
树的每个节点调用所有已配置决策器的 ConcurrentSearchRequestDecider#evaluateForQuery()
方法。最终的并发搜索决策是通过组合每个决策器返回的 ConcurrentSearchRequestDecider#getConcurrentSearchDecision()
方法的决策而获得的。