LSM Tree(log-structured merge-tree)是一种文件组织结构的数据结构,目前在不少数据库中都有使用到,如SQLite、LevelDB、HBase在Mongodb中也有一个LSM引擎;
在传统的关系型数据库中使用的是B-/B+ tree作为索引的数据结构,B tree的查询性能很高,为O(log n)复杂度,但其写性能并达不到O(log n),而在传统数据库中每次插入、删除数据都要更新索引,每次更新索引都会有一次磁盘IO,频繁写时其性能较低;
LSM Tree查询性能达不到理论的O(log n),但效率并不慢,且其避免了频繁写时的磁盘IO,使得非常适用于KV与日志型数据库;
LSM快的原因
磁盘、内存的顺序读写性能远高于随机读写性能,LSM通过消除更新操作(改、删)在其结构中数据无法改、删改,只能够顺序的新增追加,从而达到避免了随机写的性能问题;
写的情况解决了,但此时还必须解决随机读的性能问题,或者说怎么能够避免随机读;在目前顺序追加的两个场景中通过其特性消除了随机读的问题:
1、在WAL(write-ahead log)中场景中其数据是被整体访问的不存在随机读问题;
2、在Kafka中其没有随机读,因为其有明确的offset,有了offset就可通过seek读取指定数据,明确的物理偏移量;
LSM Tree要解决的是不需要读取全部数据、无需物理偏移量的读场景下的高性能读的问题;
写数据
外部数据是无序的,但LSM Tree所有写操作为顺序写,直接无差别的追加并不能虽然实现了顺序写但不能保证数据的有序;在LSM Tree中会在内存中使用一个有序结构(memtable)如(AVL树、红黑树等),写数据时都写入其有序树中,始终保持数据的有序性,当写入数据达到阈值时触发有序树的flush刷盘操作,将数据有序的顺序写入到磁盘中,生成segment,有序树开始新的周期。
SSTable
LSM Tree持久化后的数据称为SSTable(Sorted Strings Table),SSTable为按Key排序后的数据,每个SSTable可能包含多个文件成为segment,其内部数据一样为有序结构,segment为immutable(不可修改);
读取数据
先从mentable有序树中查找数据,如未找到数据则从SSTable中读取指定数据,从最新segment开始依序扫描segment,在每个segment中其内部都是有序数据,可使用二分查找算法进行查询,可在O(log n)时间内得到结果;
二分查找有两种情况:一次性把数据全部读到内存、每次二分时读取数据;在segment非常大时两者性能都不够理想,可在内存内部维护一个稀疏索引(sparse index)【稀疏索引是指将有序数据切分为固定大小的块,仅对每块开头一条数据做索引】,引入稀疏索引后,可在索引表中用二分查找定位key 在哪小块数据中,后仅从磁盘中读取一块数据即可获得查询结果,此时加载数据量仅是整个 segment 的一小部分,IO大大降低。
查找的数据存在时不需全盘扫描,但如读取的数据不存在SSTable中时,此时需要扫描所有segment才能得到最终结果,但此时性能会非常差;
修改、删除数据
在LSM Tree的SSTabe中数据是不可修改的,也就是不可修改、删除;此处的修改数据并不是传统意义上的找到某条记录并修改它,只是追加一条新的数据记录当读取数据是自然会只读到新数据从而忽略掉老的数据;删除数据同理,其删除逻辑为:追加一条数据其值为墓碑,就替换掉了老数据;当SSTable执行合并数据逻辑时,这些“删除”、“修改”的数据将会被真正的删除掉;
性能优化
这时可使用布隆过滤器(bloom filter)进行优化,写入数据之前在布隆过滤器中标记,在读取时可断定某条数据是否存在。布隆过滤器有假阳性的概率,但其认为一条数据没出现过,那该数据一定没出现过。其认为一条数据出现过,那么该数据很有可能出现过;
文件合并(Compaction)
系统运行越久SSTable的segment数据会越多,读取时的IO次数越多,性能会降低,此时需要控制segment的数量;LSM Tree会定期执行segment合并操作,将多个segment合并为一个大的segment,清理旧segment,由于segment的有序特性,使用并归排序就能快速的对数据进行合并,时间复杂度接近O(n);
memtable持久化
为避免memtable有序树数据还未持久化为SSTable文件时系统就崩溃,可在将数据写入到memtable时同时将数据写入到WAL日志中,当程序崩溃时可通过此文件恢复数据;持久化SSTable时需要对memtable与WAL日志做统一的清空处理;
LSM Tree基本流程
1、写数据:将数据缓存到内存有序树结构中(memtable),以此同时更新稀疏索引、布隆过滤器;
2、memtable达到预设的阈值,将该有序树数据flush刷盘到磁盘中,生成有序数据文件segment,此文件为immutable性质,不可修改;
3、读取数据时,如存在布隆过滤器,先用其验证如不存在数据就不存在,否则先从mentable有序树中查找数据如找到数据,依次从新到老顺序查询每个segment,查询segment使用二分查找对应稀疏索引,知道对应数据offset范围,读取磁盘范围内数据,再次二分查找获取数据;
4、LSM Tree定期执行compaction操作,将多个segment文件合并为更大的文件;