如何避免索引调节,深入分析段合并

本文探讨了Elasticsearch中段合并的工作原理及其对索引性能的影响,深入分析了默认配置下的TieredMergePolicy策略,并提供了避免索引调节的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文翻译链接Elasticsearch: How to avoid index throttling, deep dive in segments merging
如何避免索引调节,深入分析段合并
本文是基于ES5.5.0和Lucence6.6。
什么是索引段、段合并的时间和原因,以及正确的配置对如何管理好ES集群至关重要。
如果你的集群十分庞大,那默认的配置可能并不管用。不大确定为什么合并策略的文档从索引模块Index Modules中消失了,但是可以在源代码source code MergePolicyConfig.java中找到。在源码底部 the bottom你会找到max_merged_segment的重要提示,默认设置为5G。
注意,这可能意味大分片将持有gb级别的数据, max_merged_segment(5gb)的默认值可能会在索引中产生许多段,并造成搜索速度变慢。
那么这些gb级别的数据是什么?我来试图回答这个问题。
首先我极力推荐Visualizing Lucene’s segment merges by Michael McCandless。如果你对Lucene并不了解的话那你也应该先看一下 Elasticsearch from the Bottom Up。第一个链接的第三个视频告诉你可能感兴趣的分层合并策略(TieredMergePolicy),而其他的策略都在ES 1.6中被弃用了并在ES2.0中移除了,那篇文章对分层合并策略的解释十分清楚。
Lucene中关于TieredMergePolicy.findMerges的实现让我受益匪浅,这使我可以按我期望的方式理解ES的默认配置。
回到上面提到的,TieredMergePolicy 首先计算索引中有多少段是“预算”允许的,通过计算“完美的对数楼梯”(“perfect logarithmic staircase” )需要多少步才能得到总的索引大小、最小段尺寸minimum segment size()、mergeAtOnce和一个新的配置maxSegmentsPerTier,让你在楼梯的每一个阶梯上设置允许的宽度(段的数量)。这很好,因为它能同时从楼梯的宽度中分离出有多少段可以合并。
让我们回头看一下源码如何实现的。
First 首先我们得到按大小降序排序后的段信息。Collections.sort(infosSorted,newSegmentByteSizeDescending(writer));
Block 接着计算索引的总大小(所有段之和)和最小段的大小。

long totIndexBytes =0;long minSegmentBytes = Long.MAX_VALUE;for(SegmentCommitInfo info : infosSorted){finallong segBytes = size(info, writer);// ... skipped ... //

  minSegmentBytes = Math.min(segBytes, minSegmentBytes);// Accum total byte size
  totIndexBytes += segBytes;}

现在我们有两个变量,totIndexBytes 是所有索引的大小,minSegmentBytes 是最小索引段的大小。
Next下一步我们排除所有大于max_merged_segment/2.0 (默认 5gb /2就是 2.5gb)的段。

int tooBigCount =0;while(tooBigCount < infosSorted.size()){long segBytes = size(infosSorted.get(tooBigCount), writer);if(segBytes < maxMergedSegmentBytes/2.0){break;}
  totIndexBytes -= segBytes;
  tooBigCount++;}

通过上一步很容易得到.PR#219.
使用floor_segment (默认2mb),如果最小的段小于这个值

minSegmentBytes = floorSize(minSegmentBytes);

block 这步非常重要,它计算允许段的值。当线段的数量大于这个数的时候这个值将触发合并。

long levelSize = minSegmentBytes;long bytesLeft = totIndexBytes;double allowedSegCount =0;while(true){finaldouble segCountLevel = bytesLeft /(double) levelSize;if(segCountLevel < segsPerTier){
  allowedSegCount += Math.ceil(segCountLevel);break;}
  allowedSegCount += segsPerTier;
  bytesLeft -= segsPerTier * levelSize;
  levelSize *= maxMergeAtOnce;}int allowedSegCountInt =(int) allowedSegCount;

当使用默认floor_segment等于2mb的配置时,假设你有一个小于或等于floor_segment 的段,我们可以估计allowedSegCountInt可以是40段左右我们的完美的logarithmic staircase应该是这样的。

这里写图片描述
通过上文可以发现:

  • floor_segment 和index refresh 的改变可能会造成minSegmentBytes 十分高,而这将导致allowedSegCountInt 很小并造成大量的合并。
  • segments_per_tier 的改变同样会显著的改变段合并的频率。
  • max_merge_at_once 为层(级别)预留空间,并使用segments_per_tier 来设置这个层允许的分段数。(在ES默认设置为10)。

Only如果有资格的段数量多于allowedSegCountInt,lucene将为合并寻找候选对象

if(eligible.size()> allowedSegCountInt){// ...}

符合eligible 条件的是所有小于max_merged_segment/2.0的段列表,并且还没有在任何合并中。

在这个if语句下面的代码code 试图找到可能的段合并组合,在allowedSegCountInt范围内eligible.size()。
这个算法很简单,一开始从最大的段中找到N个段(N < max_merge_at_once),在合并中得到小于max_merged_segment的段大小。这就是为什么完美的对数级阶梯(perfect logarithmic staircase)不应该发生,因为这个合并策略不考虑如何合并最小的段,而是试图先去合并最大的段。
这里写图片描述

  • 4.2Gb的段因为太大而被排除(大于 max_merged_segment/2.0)。
  • 第一个用于合并的段大小是2.2Gb。这个段可以和2gb的段合并,但是不能同时和2gb和1gb的段合并,所有它会跳过1gb段而开始寻找更小的段以使得合并的段大小更接近。
  • 如果符合条件的段数仍然大于allowedSegCountInt,那么下一个合并将由1gb和段没有包含在先前的合并中段组成(再次考虑这两个条件,max_merged_segment 和max_merge_at_once)。

通过代码可知:

  • 大于max_merged_segment/2.0的段不会合并,即使被删除的文档已经超过了expunge_deletes_allowed(默认10%)。如果你最终会得到许多比maxmerged线段/2.0更大的段,并且你不断地从那删除文件——他们的空间将永远不会被回收。您将需要执行强制合并或更改配置。
  • 如果索引中有不能与任何其他段合且小于max_merged_segment / 2.0的段(我假设极不常见的情况),但有超过expunge_deletes_allowed的删除对象——这时如果没有找到其他合并那么删除文件可以被恢复。
  • 如果有一个大小是max_merged_segment/2.0 - 1byte的段,这将永远不会和任何段合并并且被allowedSegCountInt排除。我假设这是一个罕见的例子,但这确实得到了确认asked。
  • 现在我知道为什么有规律的重索引会使得搜索更快,TieredMergePolicy 策略中段的合并是无序的。这在terms词改变的时候会是个问题。因为段需要存储不同时期的词。在我的例子中4gb和2.2gb的段是一月的数据,这些段最终会和更小的段合并,比如拥有11月的10mb的段。
  • 综上所述,如果你有连续的时间数据,使用时间后缀 (YYYY-mm-DD)的分片会更好。

    现在很清楚段在Lucene是如何合并的。让我们回到ES看看索引调整,索引调整在 EngineMergeScheduler.beforeMerge中。TieredMergePolicy.findMerges中返回多于index.merge.scheduler.max_merge_count的合并数,max_merge_count 是在MergeSchedulerConfig中定义的,默认设为index.merge.scheduler.max_thread_count + 5,max_thread_count = Math.min(4, numberOfProcessors / 2)。这个值和其他索引设置一样可以动态改变(因为某些原因并没有编辑在文档中)。

curl -XPUT http://localhost:9200/*/_settings -d'{
  "index.merge.scheduler.max_merge_count": 100
}'

如果你计划修改合并策略,我推荐你调大该值,这将帮助你避免索引调整。
还有什么会造成索引调整?首先,indices.store.throttle.type为true或者indices.store.throttle.max_bytes_per_sec太小。这些设置在ES6.0中可能会被移除,索引合并将不可被调整,我推荐观察细致的段大小和使用TieredMergePolicy的算法以发现还有什么可以造成索引调整,并预测哪种类型的合并是计划中的。这将帮助你回答这个问题或许是你的分段太多或者段太大。

因为时间比较仓促,个人水平也有限,可能会有不少错误还望指正,如果有解释不清或错误的烦请参考原文,后续我也会重新校注,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值