lucene的segment与LSM的关联
what:
LSM:Log Structured Merge Trees,日志结构合并树。
LSM被设计来提供比传统的B+树或者ISAM更好的写操作吞吐量,通过消去随机的本地更新操作来达到这个目标(根本原因:磁盘随机操作慢,顺序读写快,快大约3个数量级。关联文章:https://www.cnblogs.com/sfzlstudy/p/15852383.html)。
Segment:Lucene的一个Index会由一个或多个sub-index构成。sub-index被称为Segment,每个segment中包含多个documents文件,一个segment中会有完整的正向索引和反向索引。
搜索时,segments为基本单位独立搜索每个segments文件,而后再把搜索结果合并。
why:
LSM出现的理由:
顺序写入几乎能够达到磁盘的理论值“200~300 MB/s”(例如:数据库的WAL(write-ahead log) ),但是读的时候就会非常困难。
为了提高读的效率,强加结构信息于数据上,数据被按照特定的方式放置,结果就会:对写操作不友善,让写操作性能下降。
常有用的4种方式:
1、二分查找: 将文件数据有序保存,使用二分查找来完成特定key的查找;
2、哈希:用哈希将数据分割为不同的bucket;
3、B+树:使用B+树 或者 ISAM 等方法,可以减少外部文件的读取;
4、外部文件: 将数据保存为日志,并创建一个hash或者查找树映射相应的文件;
LSM 使用一种不同于上述四种的方法,保持了数据写性能,以及微小的读操作性能损失。总体是:操作顺序化。
Segment出现理由:
1、简化了写文档的逻辑:解耦了写文档和读文档。若无,则要修改整个索引,所以会影响到文档的读。
2、提升了写文档的速度。只是创建包含单个文档的Segment,所以速度比较快;并且段里的数据都是排序好的,所以在和已有段合并的时候速度也是比较快的
how:
LSM的核心思想:
LSM树有以下三个重要组成部分:
1、MemTable:存在于内存中,用于保存最近更新的数据,按Key有序地组织。断电会丢失数据,因此通常会通过WAL(Write-ahead logging,预写式日志)的方式来保证数据的可靠性。
2、Immutable MemTable:存在于内存中,当 MemTable达到一定大小后就会变成Immutable MemTable。可理解为不可写的MemTable,是MemTable到SSTable的中间态。
3、SSTable(Sorted String Table):有序键值对集合,是LSM树在磁盘中的数据结构。为了加快SSTable的读取,可以通过建立key的索引以及布隆过滤器来加快key的查找(如下图)。
LSM树会将所有的数据插入、修改、删除等操作记录保存在内存之中,当此类操作达到一定的数据量后,再批量地顺序写入到磁盘当中(B+树数据的更新,会直接在原数据所在处修改对应的值;LSM数的数据更新,是日志直接顺序append)。
不同的SSTable中,可能存在相同Key的记录,当然最新的那条记录才是准确的。可能存在2个问题:
a、Key冗余存储。除了最新的那条记录外,其他的记录都是冗余无用的。需要进行Compact操作(合并多个SSTable)来清除冗余的记录。
b、读取时需要从最新的倒着查询,直到找到某个key的记录。会出现:不存在的key,需要将所有的SSTable找完(所以索引/布隆过滤器来优化)。
LSM树的Compact策略:
Compact操作是十分关键的操作,否则SSTable数量会不断膨胀。在Compact策略上,主要介绍两种基本策略:size-tiered和leveled
1、读放大。实际读取的数据量大于真正的数据量。例如:在LSM树中需要先在MemTable查看当前key是否存在,不存在继续从SSTable中寻找。
2、写放大。实际写入的数据量大于真正的数据量。例如:在LSM树中写入时可能触发Compact操作,导致实际写入的数据量远大于该key的数据量。
3、空间放大。实际占用的磁盘空间比数据的真正大小更多(原因是:数据冗余)。
size-tiered策略:
每层SSTable的大小相近(不同层的SSTable大小不同),同时限制每层SSTable的数量相同。
例如:每层限制SSTable为N,当每层SSTable达到N后,则触发Compact操作合并这些SSTable,并将合并后的结果写入到下一层成为一个更大的sstable。会出现:最底层的单个SSTable的大小会变得非常大。对于同一层的SSTable,每个key的记录是可能存在多份的,只有当该层的SSTable执行compact操作才会消除这些key的冗余记录。
leveled策略:
采用分层的思想,每一层限制总文件的大小,SSTable的大小相近(SSTable大小限制,和size-tiered策略相同)。但是同一层的key是全局有序的,并且唯一。
合并策略和size-tiered不同,如下:
1、L1的总大小超过L1本身大小限制:
2、从L1中选择至少一个文件,然后把它跟L2有交集的部分(非常关键)进行合并。生成的文件会放在L2
L1第二SSTable的key的范围覆盖了L2中前三个SSTable,那么就需要将L1中第二个SSTable与L2中前三个SSTable执行Compact操作。
3、L2合并后的结果仍旧超出L5的阈值大小,需要重复之前的操作 —— 选至少一个文件然后把它合并到下一层:
注意:不同层可以并发合并
Segment:
继承了LSM(Log Structured Merge Trees)中数据写入的优点,但是在查询上只能提供近实时而非实时查询。
Lucene中的数据写入会先写内存的一个Buffer(类似LSM的MemTable,但是不可读,是不可被搜索的),当Buffer内数据到一定量后会被flush成一个Segment,每个Segment有自己独立的索引,可独立被查询,但数据永远不能被更改。
Index的查询,对多个Segment进行查询并对结果进行合并,还需要处理被删除的文档。为了优化,Lucene会有策略对多个Segment进行合并(和LSM类似)。