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

举报
hw-hufeng 发表于 2020/09/22 15:08:13 2020/09/22
【摘要】 本文为了解决ES在索引合并过程中的写放大问题,深入阅读了ES/Lucene源码,从代码层面彻底摸清了ES/Lucene 索引Merge流程与实现。整个问题描述和剖析的过程,对其它有类似需求的开发者可以起到抛砖引玉的借鉴作用。

背景:

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

ES Merge过程解析

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

image.png

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

image.png

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)。

image.png

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

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

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

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

分层Force合并

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

1.     首先看当前Segment总数<maxMergeAtOnceExplicit(30),并且输出的maxSegmentCount=1,如果两个条件均满足则生成一个All-In-One MergeSpecification对象并返回。

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

image.png

ConcurrentMergeScheduler执行过程

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

image.png

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

 image.png

       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


【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。