ElasticSearch Merge机制和写放大问题研究

网友投稿 1268 2022-05-28

背景:

ES在做Segment合并的时候,根据写入模式和数据量,通常会有几倍到十几倍的写放大。此处写放大的定义是:磁盘写入数据总量/最终生成索引的大小。ES通常有两种类型的合并:NatureMerge和ForceMerge。NatureMerge是ES为了提升查询性能、回收删除Doc,在后台定期调度的merge操作。对索引的任何增删改操作都有可能触发NatureMerge。而ForceMerge是指在一次导入大批量数据后,由运维人员手动触发的merge操作,目的是减少segment数量,提升查询性能。ForceMerge由于是人工触发,通常预设当前无查询流量,所以过程比较激进,例如默认会merge成一个segment。

ES Merge过程解析

ES Merge过程可以视为一个典型的生产者-消费者模式。首先由MergePolicy根据一系列算法生成一个MergeSpecification对象。其次由MergeScheduler 执行这个MergeSpecification。

MergePolicy类图如上所示,当前ES/Lucene的默认Merge策略是TieredMergePolicy。具体算法流程解析稍后给出。在基类中的findMerges/findForcedMerges分别对应前述的NatureMerge和ForceMerge过程。

MergeScheduler的类图如上所示,ES/Lucene默认实现为ConcurrentMergeScheduler。在Lucene层面,如果用户不想触发Merge,可以把默认MergePolicy和MergeScheduler分别指定为NoMergePolicy和NoMergeScheduler。但是ES目前并不开放这个配置。

分层Nature合并:

在前文中提到ES/Lucene的默认merge策略是TieredMergePolicy,即分层Merge。注意此处层只是一个按Segment大小划分的逻辑概念,在文件系统和ES架构中不同层的索引并无本质区别。分层Merge主流程主要分三步:

1.     根据分层算法和deletesPctAllowed配置推导出本次Merge完成后AllowedSegCount和AllowedDelCount。这两个值是后续循环中止条件。分层算法的逻辑非常简单:比如当前索引总量为20M,AllowedSegCount为10个(10*2M)。当前索引总量为220M,AllowedSegCount为20个(20M*10 + 2M*10)。依此类推。

2.     滑动窗口算法寻找OneMerge对象。如下图所示:对候选Segments按大小排序,通过一个滑动窗口从左往右滑动。窗口包含的Segment数从1开始,最大不超过maxMergeAtOnce(默认10)。同时窗口内的SegmentSize总和不超过maxMergedSegmentByte(默认5G)。

3.     对第二步选出来的OneMerge对象进行打分(分数越低越优)。打分考虑如下三个因素,考虑权重依次降低:

a)     选中Segment的大小平均度,越平均越好

b)     选中Segment中可回收Doc的比率,越高越好。

c)      选中SegmentByte总和,越小越好

分层Force合并

ElasticSearch Merge机制和写放大问题研究

由于Force合并是手工触发,并不考虑当前服务吞吐和延迟。所以策略比Nature合并简单粗暴很多。

1.     首先看当前Segment总数

2.     如果上述条件不满足,则依旧采用滑动窗口算法。但是和Nature合并相反,Force合并是从右向左滑动。窗口的初始值为2,不超过maxMergeAtOnceExplicit,并且窗口内SegmentSize总和不超过maxMergedSegmentByte。

ConcurrentMergeScheduler执行过程

ConcurrentMergeScheduler实际上是对一个后台线程池的封装。当设备硬件为传统磁盘时,启动1个线程。当年设备硬件为SSD固态硬盘时,启动的线程数为max(1, min(4, core/2))。同时工作的MergeCount = ThreadCount + 5。ConcurrentMergeScheduler执行流程如下图所示:

由上图可知,整个流程其实就是对线程池的调用,虚线部分表示这是一个线程池Push的异步操作,并不需要等待merge工作实际完成。整个Merge过程实际上分四步,分别是mergeInit,mergeMiddle,mergeSuccess和mergeFinish。实际干活的事情都在mergeMiddle中实现。MergeMiddle借助SegmentMerger封装,对FieldInfo、倒排、正排等索引结构做依次Merge。Merge过程如下:

Lucene借助Codec的抽象,将索引处理流程和索引数据结构解耦开。右边是Merge流程,左边是每个数据结构对应的Codec。Codec中包含输入数据的Consumer/Producer。分别负责生成索引和读取索引。

小结

由上文可知,整个Lucene索引Merge的流程并不复杂。通过Policy/Scheduler将索引合并的描述MergeSpecification的生成和执行解耦。开发者可以根据自己的业务场景需要,自由灵活的组装。而Merge的实际工作MergeMiddle主要依赖SegmentMerger类实现。

附录1:ES/Lucene对Merge写放大有影响的参数

参数名

含义

默认值

备注

max_merge_at_once

一次普通merge可以参与的segment数量

10

max_merge_at_once_explicit

一次forcemerge可以参与的segment数量

30

推荐适度调大,可降低写放大。

max_merged_segment_bytes

OneMerge产出的segment最大值

5G

对于小规模索引够用,对于海量索引数据推荐调大。如调为0则不触发Nature Merge

附录2:参考文献

Lucene-8.6源码:https://github.com/apache/lucene-solr

Lucene官方API文档:https://lucene.apache.org/core/8_6_2/core/index.html

Solar官方文档: https://lucene.apache.org/solr/guide/8_6/

http://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html

云搜索服务 CSS

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Linux nc 命令
下一篇:记录一次FastDFS服务在增加新组之后报错的解决过程
相关文章