Apache Lucene中的并发查询执行

Apache Lucene是一个出色的并发纯Java搜索引擎,如果您愿意,它可以轻松地使服务器上的可用CPU或IO资源饱和。 “典型” Lucene应用程序的并发模型在搜索时每个查询一个线程,但是您知道Lucene还可以使用多个线程同时执行一个查询以大大减少最慢查询的时间吗?

Lucene的IndexSearcher类负责执行传入查询以从索引中查找最匹配的匹配项,它接受一个可选
施工期间执行器 (例如线程池)。 如果您通过Executor并且CPU足够闲置(即,服务器远低于其红线QPS吞吐能力),Lucene将使用多个并发线程来查找每个查询的总点击率最高。


它是如何做到的? Lucene索引是分段的 ,这使搜索成为一个棘手的并行问题:每个查询都必须访问索引中的所有细分,并收集其具有全球竞争力的点击量。 当查询为单线程查询时,因为您没有将Executor传递给IndexSearcher ,所以一个查询线程必须按顺序访问所有段。 如果索引很大,并且您的查询成本很高,那么这些查询自然会需要较高的CPU成本和挂钟时间才能找到热门广告。 即使您在远远低于其红线QPS(吞吐量)容量的情况下运行服务器,这也会导致高长杆(P90 +)查询延迟。

相反,当您将Executor传递给IndexSearcher ,索引中的段首先被IndexSearcher分组为单个线程工作单元,称为
螺纹片默认情况下 ,大段属于它们自己的线程片,最多5个较小的段(最多250K总文档)将合并为一个线程片,因为它们可以快速地按单个线程顺序搜索。 通过子类化IndexSearcher并覆盖其受保护的slices方法,可以轻松地自定义将段合并为线程片的方式。 只要服务器闲置到足以在一个查询上花费多个CPU内核,并且该查询的每个线程片上都有一个线程,那么每个并发的查询就会同时执行。

这个强大的功能最初是由Jean-FrançoisHalleux于16年前提出的 ,然后由Doug Cutting自己 (您好,Doug!)提出,并大约9年前最终重构为IndexSearcher ,此后进行了许多迭代的改进,许多现在都在不断发展。感谢Atri Sharma ,最近添加了新的Lucene / Solr提交者 。 这就是热情的开源软件开发的分布式力量!

并发查询执行是Lucene中很少有人知道的sleeper功能,因为它尚未在基于Lucene构建的两个流行的分布式搜索应用程序ElasticsearchSolr中公开。 他们的并发模型是跨索引分片(通常在不同的服务器上)针对单个查询的并发搜索,但是在每个分片内使用单线程搜索。

这意味着需要许多并发的独立查询才能使集群范围的CPU或IO资源饱和。 直到群集至少看到最低最低QPS,才能使用全部硬件资源。 对于经常看到高查询率的用例,此限制是可以接受的。 但是,如果Elasticsearch或Solr使用此功能,则具有较大索引和较低查询率的其他常见用例将从单个群集节点内的并发查询执行中受益匪浅。

摩尔定律在现实世界中的影响已经发生了变化:现代服务器级计算机是用惊人且Swift增长的并发硬件构建的,不仅在其CPU中,我们现在在最新的c5.24xlarge AWS EC2实例中还可以看到96个内核,图形处理单元(GPU),内存总线,DIMM和固态磁盘(SSD),实际上是底层的大型并发RAID 0阵列。 最近的趋势是CPU和GPU获得更多的并发(内核),而每个单独的内核获得的并发速度则更快。 为什么不使用所有这些增加的并发性来提高所有查询的速度,甚至在低查询负载时也使CPU / IO饱和?

棘手的权衡

不幸的是,尽管搜索Lucene索引是一个自然而尴尬的并行问题,但对一个查询使用多个线程会产生固有的协调开销。 要理解为什么,请考虑一个简单的类比:假设您需要苹果,那么您将孩子送到当地的杂货店购买。 如果您只有一个孩子,则将其送给她,她会在整个农产品区域四处走走,挑选十个最好的苹果,然后带回家。

但是,如果您有五个孩子,然后将所有孩子都送到商店,他们会不会再快五倍,而忽略了他们往返商店的“联网”时间? 他们如何有效地分割工作?

也许您的孩子很聪明,他们首先将商店中的所有苹果部分(现在有很多苹果选择 !)分成五个大致相等的部分。 每个人都围绕着自己的苹果区运行,挑选她能找到的十个最好的苹果,然后他们都在结帐柜台集合,密切合作,从现在拥有的五十个苹果中选出十个最好的苹果? 这有点浪费,因为孩子们总共收集了五十个苹果,只是为了最终选择实际的十个最佳苹果,但确实比一个孩子选出十个最佳苹果要快。

这实际上是Lucene今天实现并发搜索的方式:每个搜索器线程单独工作以从一个线程片中找到自己的前N个最佳匹配(“映射”阶段),然后,一旦所有查询线程完成并重新加入主线程在主线程中,主线程使用部分合并排序从为每个线程切片收集的匹配中找到总前N个最佳匹配(“减少”阶段)。 Lucene的CollectorManagerCollectorLeafCollector抽象都协同工作以实现此目的。 从现在开始,这意味着与单线程情况相比,完成了更多的工作
收集了M * N总匹配,然后最后减少到前N ,其中M是并发搜索线程的数量, N是请求检索的顶级匹配的数量。

并发运行每个查询时,增加的协调成本必然会损害搜索节点的红线QPS容量(吞吐量),因为Lucene会花费更多的总CPU周期来查找最热门。 但是,与此同时,当搜索节点具有大量备用CPU资源时,它可以大大提高长杆查询的等待时间,因为最困难的查询现在可以同时运行。 此外,收集更多匹配并最终合并它们的额外成本通常对总体影响不大,因为通常是每个匹配的匹配和排名决定了总查询成本,尤其是随着索引变大,并且该成本是有效地跨线程拆分。

您可以通过限制可以同时运行的查询数来进一步“扩大”这种折衷,从而最大化每个查询将使用多少个CPU内核。 您还可以预先估算每个查询的成本,并仅在其成本足够大时并发执行该查询,以便可以在单个线程中快速运行的简单查询不会支付在多个线程之间进行同步的开销。

这种吞吐量与延迟之间的权衡令人沮丧,这意味着在您的Lucene应用程序中使用模式方法可能很有意义。 集群负载较轻时,通过限制可以同时运行的查询数来减少每个查询的多个线程,从而减少长杆延迟。 但是,当群集正在运行时,接近其红线容量时,每个查询将转移到单个线程以最大化吞吐量。 确保您正确地测量了等待时间,并且负载测试客户端没有遭受普遍常见的协调遗漏错误 ! 确认您的负载测试客户端正在使用开环测试,以便您看到真正的延迟影响,例如长时间的垃圾收集暂停,I / O打ic或交换。

持续的和未来的改进

幸运的是,最近进行了一些激动人心的改进,以减少多线程查询的额外开销。 Lucene现在还使用传入(调用)线程来帮助并发搜索 。 用于将小段分组为切片(线程工作单元)的算法已得到改进 。 现在,提前终止现在可以在多个搜索线程中使用一个共享的全局命中计数器来查询一个查询,从而降低了查询的总成本。 查询缓存将很快使用Executor进行并发缓存,并且在某些情况下使用Executor时甚至可以更高效。 他们应该在共享信息的同时共享信息,例如到目前为止收集的最差得分的最高命中 ,甚至在所有线程中使用单个共享优先级队列,而不是每个搜索线程都完全独立地工作并仅在最后合并热门命中。 共享优先级队列可能会导致过多的锁定,因此,作为一种折衷,现在搜索可以高效地共享搜索器线程中收集到的最差命中值中的最好值 ,这显示了令人印象深刻的luceneutil 基准测试结果


这些改进减少了并发搜索的额外成本,但是该成本永远不会为零,因为更频繁的线程上下文切换,共享优先级队列的锁争用,命中计数器和优先级队列底部以及潜在的困难后果都会带来固有的自然成本。现代非均匀内存架构(NUMA)

Lucene并发搜索的一个令人惊讶且令人失望的局限性在于,完全合并的索引(直至单个段)会丢失所有并发性! 这就是Bizarro World ,因为通常可以将其索引合并到一个段中以提高查询性能! 但是,当您查看长杆查询延迟时,不幸的是,完全合并的索引会变慢,因为即使将Executor传递给IndexSearcher所有查询现在都将再次成为单线程。 即使单个新近完成的大型合并也会在您的长极点延迟中导致锯齿状的模式,因为这会减少净查询并发,尽管通过这种合并红线群集的吞吐量仍会提高。 解决这个问题的一个简单方法是允许多个线程搜索一个大的段 ,这很有效,因为Lucene具有自然的API,可以在段的“ docid空间”中搜索单独的区域。

自让-弗朗索瓦·哈勒克斯(Jean-FrançoisHalleux)首次为Lucene提出并行搜索以来,并发搜索已经走了很长一段路,我希望它还有很长的路要走,以使我们真正减少使用多线程进行昂贵查询的额外开销。 随着Lucene改进其查询计划和优化,我们将达到一个容易查询运行单线程但代价高昂的查询同时高效运行的地步。 这些改进必须归功于Lucene:现代服务器继续添加越来越多的内核,但并没有使这些内核变得更快,因此,包括Lucene在内的现代软件不可避免地必须找到有效利用所有这些并发性的方法。

[我在亚马逊工作,并且本网站上的帖子属于我自己,不一定代表亚马逊的职位]

翻译自: https://www.javacodegeeks.com/2019/10/concurrent-query-execution-apache-lucene.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值