Lucene's MergePolicy

Lucene’s MergePolicy

Lucene很多特征,在我看来跟LSM-Tree的数据库非常相似,甚至很多问题的解决方式都如出一辙。这里我想跟大家来聊聊Lucene的Segment合并问题,这个问题同样发生LSM-Tree数据库(HBase)。

我们知道我们每次冲刷索引时,Lucene都会生成一个Segment。类似就是每个MemStore的冲刷势必会产生一个HFile的道理是一样一样的。这里我想跟大家分享的是Lucene的合并策略,前面那长长铺垫如有兴趣的同学可以帮忙看一下,否则直接看Lucene的合并策略部分也无妨。

一、Segment很多会怎么样?

我们已经知道所有的索引结构都是树,因为树结构能带来比较高查找效率,同时对磁盘也更友好。我们也知道树型结构跟磁盘一样并不适合做插入写操作,因此不过是Lucene还是HBase都是在内存上先构建完一个棵后才最终落盘的。方便容易理解和描述,(在理想环境下)我们假定在某种极限条件下,使得每个文档都会触发冲刷产生Segment。显然此时每个Segment文件都有且只有一个文档,此我们查找效率被完全降级变成线性查找。
如果是每两个文档一起才会被冲成,那么它的查询效率将会是$O(logN)$。在这种假设下,我们可以理解成查找效率最终会是$O(K)$(K为Segment的数量)。

当然经验告诉我们这是不可能的,因为Segment内进行查询也是需要时间的。当文件越来越大,Lucene在Segment里进行查找时它的IO代价会越来越高。即是需要把索引加载到内存的代价也越来越高了,当然Lucene采用的一个非常先进FST(Finite-State-Transfer,能够有效减少加载代价,这里先不展开介绍)。

那我们结论是,当Segment在一个适当大小时,我们查找效率会很高。所以我们不想让Semgnet文件太多,也不想让Segment文件太大。

二、所以需要合并

基于以上结论,我们希望总能将那些不太大的Segment合并成另一个不太大的Segment以获取更高查找效率。不管Lucene还是HBase(或者是其它LSM Tree结构数据库)都有合并过程,合并主要是有以下两个大作用。
1. 减少文件的个数提高性能;
2. 清除无效文档(被标识为删除的文档)。

跟LSM-Tree的数据库一样都有合并的问题,而且功能也都雷同。目的就是为清除无效的数据,同时减少文件数量。

2.1 为什么只有追加

因为磁盘跟数组一样,删除和插入都非常不友好,为了更极致的性能,我们将所有操作变成顺序写。

理论上,Lucene/LSM-Tree并没有更新和删除操作。这些操作都是通过特殊的手段来实现,从磁盘的角度看都是写入。在Lucene/LSM-Tree做一个更新时尽管与原文档长度完全一样,也只能将原文档标记为删除,同时再追加一条新记录。
只因为他们在方面都有显著的特点,即是Append only,顺序写入。

因此他的写性能特别高(线性时间复杂度),但需要消耗额外资源来完成清除和合并的操作。所以说,所有的美好的都是有人代价。在写时拥有非常优秀的表现的同时需要被后期消耗甚至更多的代价来优化读的性能。当然合并就是读优化的一部分,读优化还有其它很多很多内容。

2.2 Lucene索引合并策略

竟然合并势在必行,那我们只能寄希望于每次合并都是最有效率的。那么问题来了,怎么合并怎么才是有效的呢?为此,出生比较早的Lucene有以下实践(据悉HBase也走过相当的路)。

Lucene提供如下三大MergePolicy,后面Solr又扩展了一种叫SortingMergePolicy。

  1. LogMergePolicy
    1. LogByteSizeMergePolicy
    2. LogDocMergePolicy
  2. TieredMergePolicy
  3. UpgradeIndexMergePolicy

TieredMergePolicyLucene4.0成为Lucene的默认合并策略。

老实说最后一种(UpgradedIndexMergePolicy)我也不了解它。也没用过,也鲜有人提及。

2.2.1 LogMergePolicy

这里先来介绍一下LogMergePolicy吧,这种合并策略非常好理解,她提供一种定长的合并方式。我们只介绍LogByteSizeMergePolicy,因为LogDocMergePolicy与LogByteSizePolicy的原理一样。LogByteSizeMergePolicy描述了合并条件的上水位和下水位,默认的上水位是2Gb。即是捡出一堆不超出上限的Segmnet进行合并,且将这一堆Segment分成若干层,默认上限是2GB

在LogMergePolicy的实现上,总能感觉到作者在设计这个策略的时候,已经是假设Segment文件大小基本符合梯形分布。

实际上LogMergePolicy的实现有些晦涩难懂,不过不用着急,都坐下听我说。如果Segment文件的大小总能大体符合梯形分布的,且从左到右按从大到小排列的。然后在这个系列中找到最大的Segment,并把其大小记为max,然后把max减去一个跨度记为bottom。同时从右向左遍历找到第一个Segment文件大小小于等于bottom的Segment,并把其下标记为upto。然后把从左开始位置start=0,到upto之间的Segment作为第一层级。然后把start=upto+1,又一次从最右边开始找到bottom所有Segment,这个区间称为第二层级。以此类推至将所有Segment都划分完成。

然后根据MergeFactor(合并因子,默认为10)把一个层级拆分成多个合并单元,拆分合并单元的规则是根据start到upto之间的Segment的个数size做如下处理:
1. size < 10,start=upto+1,即是放弃整个Level
2. Size = 10*N,拆分N个合并单元
3. size = 10*N + m,把前10*N拆分N个合并单元,把最后m个Semgent扔掉

值得注意是如果这个合并单元里有一个Segment越界或者正处于合并状态或待合并状态的,这个合并单元会被放弃。

说一千道一万,我觉得都是废话。下面用一句话来概括一下这个合并策略:

只能与相邻的Segment进行合并,不管邻居们是高大还是矮小,它们最后总会合并在一起。

原本以为,按Segment编码排序从左向右选取一定量的Segment进行合并,同时还引入了一个很复杂的分层算法。讲道理的话,Segment按文件大小跟Segment编号成反比。若用一个柱体来表示文档的大小话,柱体最终应该总能形成是一个梯形分布。然而事实上事与愿违,他最终分布往往是不规则的。即是理想状态下,segment文件是按梯形分布的。然而现实非常现实却是那么的残酷,总是能出现小文档跟大文档发生合并。

那么在这个范围内的索引都会被拉进来发生合并,而且在极端是条件下,几兆的文件在跟上千兆的文件在合并。这相当于什么呢,相当于一个1/2Gb的文件在移来移去。这是非常耗资源的过程,而且带来的效益无乎没有。

小文档跟大文档合并会怎么样?

首先是Lucene/LSM-Tree都是Append Only的,所以当出现合并的时候,实际上是把原Segment读出来重新写入磁盘变成一个新Segment。

好了,开始“如果”之路。
如果我有两个Segment,一个有10万文档,文件大小为2Gb;另一个有1个文档,文件大小为1Kb。现在来了,此时是不是可以理解为把大文件重新写一遍。如果你的Lucene总是了发生这样的事的话,那你的Lucene的Merge队列将会被阻塞,你的系统十分耗机器的资源,估计也正处于崩溃的边缘了。

到这里我们可以清晰的知道,LogMergePolicy的设计是有缺陷的MergePolicy。

2.2.2 TieredMergePolicy

既然文档合并是一个不可规避的问题,那我们只能让每次合并有更大效益。

实际上LogMergePolicy分层就是为了解决合并单元的Segment大小差异过于悬殊的问题,只是说没有解决好而已。因此才能引入TieredMergePolicy一个全新合并策略。经过以上的描述其实,不难知道应该怎么避免这个问题。其最终目的是非常清楚的,就是宁可少发生一些合并,也不要将大文件跟小文件合并。

TieredMergePolicy在每一次捡起时,都会先做一次排序,按文件大小排序。因此TieredMergerPolicy已经打破了必须与相邻的Segment才能合并的规则,从全局的眼界去挑选更合适的,大小更接近的Segment进行合并。

TieredMergePolicy的实现是挺复杂,也很难懂的。早期Lucene的代码着实有些天秀的感觉,常常会被代码组织得非常晦涩。往后的代码,也能看到很多难懂的代码被重构了,可读性越来越高。

最后介绍一下TieredMergePolicy的实现粗节,
1. Segment先按从大到小排序
2. 统计并过滤大于预设最大Segment的一半的Segment
3. 过滤掉正处于合并状态,或者已经在待合并状态的Segment
4. 如果一层里的合并后的总大小超过设定的最大值(默认5Gb),该层被扔掉
5. 对每层侯选集进行打分,依据是合并后的文件大小以及删除率

TieredMergePolicy总能避免选中大文件出来进行合并,这些大文件一般都是以为更高的删除率时,或者系统比较空闲的时候会被合并。由些可见TieredMergePolicy总选择合并代价最低,且结果往往能捡出性价比更高的Segment进行合并。

三、结语

TieredMergePolicy如果没有记错应该是大神Mike带来,出生于3.x并在4.0时升级为默认的MergePolicy。据大神Mike表示,TieredMergePolicy带来8x的性能提升。

虽然说MergePolicy问题,近期并没有在Lucene的issue中提及了。我也是偶然整理自己的笔记的时候发这个topic,因此整理整理发出来与大家分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章