3.6 控制索引合并
读者知道(我们已经在第1章中讨论过),在ElasticSearch中每个索引都会创建一到多个分片以及零到多个副本,也知道这些分片或副本本质上都是Lucene索引,而Lucene索引又基于多个索引段构建(至少一个索引段)。索引文件中绝大部分数据都是只写一次,读多次,而只有用于保存文档删除信息的文件才会被多次更改。在某些时刻,当某种条件满足时,多个索引段会被拷贝合并到一个更大的索引段,而那些旧的索引段会被抛弃并从磁盘中删除,这个操作称为段合并(segment merging)。
也许你会有疑问,为什么非要进行段合并?这是因为:首先,索引段的个数越多,搜索性能越低且耗费内存更多。另外,索引段是不可变的,因而物理上你并不能从中删除信息。也许你碰巧从索引中删除了大量文档,但这些文档只是做了删除标记,物理上并没有被删除。而当段合并发生时,这些标记为删除的文档并没有复制到新的索引段中。如此一来,这减少了最终索引段中的文档数。
频繁的文档更改操作会导致大量的小索引段,从而导致文件句柄打开过多的问题。我们必须要对这种情况有所准备,如修改系统配置,或设置合适的最大文件打开数。
从用户角度来看,段合并可快速概括为如下两个方面:
- 当多个索引段合并为一个的时候,会减少索引段的数量并提高搜索速度。
- 同时也会减少索引的容量(文档数),因为在段合并时会移除被标记为已删除的那些文档。
尽管段合并有这些好处,但用户也应该了解到段合并的代价,即主要是I/O操作的代价。在速度较慢的系统中,段合并会显著影响性能。基于这个原因,ElasticSearch允许用户选择段合并策略(merge policy)及存储级节流(store level throttling)。本章后续部分将会讨论段合并策略,而存储级节流则安排在6.2节中讨论。
3.6.1 选择正确的合并策略
尽管段合并是Lucene的责任,ElasticSearch也允许用户配置想用的段合并策略。到目前为止,有三种可用的合并策略:
tiered(默认)
- log_byte_size
- log_doc
前面提到的每一种段合并策略都有各自的参数,而这些参数定义了各自的行为特点,并且它们的默认值都是可以覆写的(请阅读后续章节来了解这些参数)。
为了告知ElasticSearch我们想使用的段合并策略,可以将配置文件的index.merge.policy.type字段配置成我们期望的段合并策略类型。例如下面这样:
一旦使用特定的段合并策略创建了索引,它就不能被改变。但是,可以使用索引更新API来改变该段合并策略的参数值。
接下来我们来了解这些不同的段合并策略,以及它们提供的功能,并讨论这些段合并策略的具体配置。
tiered合并策略
这是ElasticSearch的默认选项。它能合并大小相似的索引段,并考虑每层允许的索引段的最大个数。读者需要清楚单次可合并的索引段的个数与每层允许的索引段数的区别。在索引期,该合并策略会计算索引中允许出现的索引段个数,该数值称为阈值(budget)。如果正在构建的索引中的段数超过了阈值,该策略将先对索引段按容量降序排序(这里考虑了被标记为已删除的文档),然后再选择一个成本最低的合并。合并成本的计算方法倾向于回收更多删除文档和产生更小的索引段。
如果某次合并产生的索引段的大小大于index.merge.policy.max_merged_segment参数值,则该合并策略会选择更少的索引段参与合并,使得生成的索引段的大小小于阈值。这意味着,对于有多个分片的索引,默认的index.merge.policy.max_merged_segment则显得过小,会导致大量索引段的创建,从而降低查询速度。用户应该根据自己具体的数据量,观察索引段的状况,不断调整合并策略以满足应用需求。
log byte size合并策略
该策略会不断地以字节数的对数为计算单位,选择多个索引来合并创建新索引。合并过程中,时不时会出现一些较大的索引段,然后又产生出一些小于合并因子(merge factor)的索引段,如此循环往复。你可以想象,时而有一些相同数量级的索引段,其个数会变得比合并因子还少。当碰到一个特别大的索引段时,所有小于该级别的索引段都会被合并。索引中的索引段个数与下次用于计算的字节数的对数成正比。因此,该合并策略能够保持较少的索引段数量并且极小化段索引合并的代价。
log doc合并策略
该策略与log_byte_size合并策略类似,不同的是前者基于索引的字节数计算,而后者基于索引段的文档数计算。以下两种情况中该合并策略表现良好:文档集中的文档大小类似或者你期望参与合并的索引段在文档数方面相当。
3.6.2 合并策略配置
我们现在已经知道索引的段合并策略的工作原理了,只是还缺乏配置方面的相关知识,所以现在就来讨论每种合并策略及其提供的配置选项。请记住,大多数情况下默认选项是够用的,除非有特殊的需求才需要修改。
配置tiered合并策略
当使用tiered合并策略时,可配置以下这些选项:
- index.merge.policy.expunge_deletes_allowed:默认值为10,该值用于确定被删除文档的百分比,当执行expungeDeletes时,该参数值用于确定索引段是否被合并。
- index.merge.policy.floor_segment:该参数用于阻止频繁刷新微小索引段。小于该参数值的索引段由索引合并机制处理,并将这些索引段的大小作为该参数值。默认值为2MB。
- index.merge.policy.max_merge_at_once:该参数确定了索引期单次合并涉及的索引段数量的上限,默认为10。该参数值较大时,也就能允许更多的索引段参与单次合并,只是会消耗更多的I/O资源。
- index.merge.policy.max_merge_at_once_explicit:该参数确定了索引优化(optimize)操作和expungeDeletes操作能参与的索引段数量的上限,默认值为30。但该值对索引期参与合并的索引段数量的上限没有影响。
- index.merge.policy.max_merged_segment:该参数默认值为5GB,它确定了索引期单次合并中产生的索引段大小的上限。这是一个近似值,因为合并后产生的索引段的大小是通过累加参与合并的索引段的大小并减去被删除文档的大小而得来的。
- index.merge.policy.segments_per_tier:该参数确定了每层允许出现的索引段数量的上限。越小的参数值会导致更少的索引段数量,这也意味着更多的合并操作以及更低的索引性能。默认值为10,建议设置为大于等于index.merge.policy.max_merge_at_once,否则你将遇到很多与索引合并以及性能相关的问题。
- index.reclaim_deletes_weight:该参数值默认为2.0,它确定了索引合并操作中清除被删除文档这个因素的权重。如果该参数设置为0.0,则清除被删除文档对索引合并没有影响。该值越高,则清除较多被删除文档的合并会更受合并策略青睐。
- index.compund_format:该参数类型为布尔型,它确定了索引是否存储为复合文件格式(compound format),默认值为false。如果设置为true,则Lucene会将所有文件存储在一个文件中。这样设置有时能解决操作系统打开文件处理器过多的问题,但是也会降低索引和搜索的性能。
- index.merge.async:该参数类型为布尔型,用来确定索引合并是否异步进行。默认为true。
- index.merge.async_interval:当index.merge.async设置为true(因此合并是异步进行的),该参数值确定了两次合并的时间间隔,默认值为1s。请记住,为了触发真正的索引合并以及索引段数量缩减操作,该参数值应该保持为一个较小值。
配置log byte size合并策略
当采用log_byte_size合并策略时,可配置以下选项:
- merge_factor:该参数确定了索引期间索引段以多大的频率进行合并。该值越小,搜索的速度越快,消耗的内存也越少,而代价则是更慢的索引速度。如果该值越大,情形则正好相反,即更快的索引速度(因为索引合并更少),搜索速度更慢,消耗的内存更多。该参数默认为10,对于批量索引构建,可以设置较大的值,对于日常索引维护则可采用默认值。
- min_merge_size:该参数定义了索引段可能的最小容量(段中所有文件的字节数)。如果索引段大小小于该参数值,且merge_factor参数值允许,则进行索引段合并。该参数默认值为1.6MB,它对于避免产生大量小索引段是非常有用的。然而,用户应该记住,该参数值设置为较大值时,将会导致较高的合并成本。
- max_merge_size:该参数定义了允许参与合并的索引段的最大容量(以字节为单位)。默认情况下,参数不做设置,因而在索引合并时对索引段大小没有限制。
- maxMergeDocs:该参数定义了参与合并的索引段的最大文档数。默认情况下,参数没有设置,因此当索引合并时,对索引段没有最大文档数的限制。
- calibrate_size_by_deletes:该参数为布尔值,如果设置为true,则段中被删除文档的大小会用于索引段大小的计算。
- index.compund_format:该参数为布尔值,它确定了索引文件是否存储为复合文件格式,默认为false。可参考tiered合并策略配置中该选项的解释。
配置log doc合并策略
当使用log_doc(文档数对数)合并策略时,可配置以下这些选项: - merge_factor:与log_byte_size合并策略中该参数的作用相同,请参考前面的解释。
- min_merge_docs:该参数定义了最小索引段允许的最小文档数。如果某索引段的文档数低于该参数值,且merge_factor参数允许,就会执行索引合并。该参数默认值为1000,它对于避免产生大量小索引段是非常有用的。但是用户需要记住,将该参数值设置过大会增大索引合并的代价。
- max_merge_docs:该参数定义了可参与索引合并的索引段的最大文档数。默认情况下,该参数没有设置,因而对参与索引合并的索引段的最大文档数没有限制。
- calibrate_size_by_deletes:该参数为布尔值,如果设置为true,则段中被删除文档的大小会在计算索引段大小时考虑进去。
- index.compund_format:该参数为布尔值,它确定了索引文件是否存储为复合文件格式,默认为false。可参考tiered合并策略配置中该选项的解释。
与前面介绍的合并策略类似,上面提及的属性需要以index.merge.policy为前缀。例如,要设置min_merge_docs属性,则应该设置index.merge.policy.min_merge_docs属性。
除此之外,log_doc合并策略支持index.merge.async和index.merge.async_interval属性,就像tiered合并策略那样。
3.6.3 调度
除了可以影响索引合并策略的行为之外,ElasticSearch还允许我们定制合并策略的执行方式。索引合并调度器(scheduler)分为两种,默认的是并发合并调度器ConcurrentMerge-Scheduler。
并发合并调度器
该调度器使用多线程执行索引合并操作,其具体过程是:每次开启一个新线程直到线程数达到上限,当达到线程数上限时,必须开启新线程(因为需要进行新的段合并),那么所有索引操作将被挂起,直到至少一个索引合并操作完成。
为了控制最大线程数,可以通过修改index.merge.scheduler.max_thread_count属性来实现。一般来说,可以按如下公式来计算允许的最大线程数:
如果我们的系统是8核的,那么调度器允许的最大线程数可以设置为4。
顺序合并调度器
该调度器非常简单,它使用同一个线程执行所有的索引合并操作。在执行合并时,该线程的其他文档处理都会被挂起,从而索引操作会延迟进行。
设置合并调度
为了设置特定的索引合并调度器,用户可将index.merge.scheduler.type的属性值设置为concurrent或serial。例如,为了使用并发合并调度器,用户应该如此设置:
如果想使用顺序合并调度器,用户则应该像下面这样设置: