log-structured merge-tree(LSM tree)是可支持大量写的一种数据结构,写操作被优化成顺序写,LSM树是许多数据库背后的核心数据结构,包括BigTable, Cassandra, Scylla, 和 RocksDB.
SSTables
LSM树在磁盘上以有序表(Sorted String Table)的形式存在。下图所示,SSTable存储了以key排序的key-value键值对,其中name为key。一个SSTable是由多个排序的文件组成,每个文件是一个Segment, 这些文件一旦写入磁盘后就不可变。
上图中每个Segment中以key有序存储,下面我们将讨论一个Segment是如何产生的。
写数据
LSM树是按顺序执行写操作的,也许你会好奇我们按任意顺序执行写操作,数据在磁盘中是如何最终变成有序的,这其实是通过一种内存树结构来实现的,也称之为memtable, 底层实现通常是一种有序树,比如红黑树。一旦有写操作,数据会被加入这颗红黑树中。
数据在这颗红黑树中会变得有序,我们会预先设置红河树的大小,一旦节点个数达到预定值,就会将这颗树中数据按序持久到磁盘形成一个Segment文件,即使我们的插入操作是无序的,最终生成Segment文件时是顺序写。
译者注:通常为了防止宕机内存数据丢失,写内存同时会写WAL,WAL是顺序写磁盘,速度很快
读操作
如何在SSTable中查找数据?可以从最新的Segment反向扫描到最老的Segment,这样就能最终找到我们需要的数据,这意味着最近写入的数据能更快的被找到,一种优化方式是对每个Segment在内存中维持一个稀疏索引。
使用索引可以快速定位需查找数据在Segment中起始范围值,根据这些起始值每个Segment只须扫描一小部分。例如,在上面这个Segment中查找key为dollar的数据,首先在稀疏索引中通过二分查找发现dollar在dog和downgrade之间,这样只需在位置17208和19504之间查找dollar,当然如果找不到就说明dollar不存在。
稀疏索引可提高查找速度,但如果查找一个并不存在的数据,依然要对所有Segment都扫描一遍,并且每个Segment都会返回不存在,这时可使用BloomFilter来提高查找速度。BloomFilter利用少量空间就能准确判断数据是否不存在,当插入数据时也将数据加入BloomFilter,查询时首先检查BloomFilter中是否存在,这样当查找一个并不存在的数据时就能快速返回。
Compaction
随着系统持续运行,将会产生越来越多的Segment文件,这些文件需要清理和维护以防止文件过多失去控制,这个过程称作Compaction。Compaction是一个后台进程,不断合并旧Segment形成新Segment。
上图中dog同时存在Segment1和Segment2中,合并后的Segment包含最新写入的数据,因此Segment4中的dog来自Segment2。一旦旧Segment中数据被合并进新Segment,旧Segment文件会被删除。
删除操作
上面讨论了读和写,那么删除呢?怎么从不可变的Segment文件中删除数据呢?删除其实是通过写操作来实现,删除一个key的数据时,通过对这个key写tombstone来标记删除。
上图展示dog曾经值为52,现在被标记为tombstone。此时查找key为dog时将返回不存在,这意味着被删除数据同样会占据磁盘空间,直到Compaction被标记为tombstone的数据才会真正从磁盘删除。
总结
现在我们理解了一个基于LSM树的存储引擎的工作原理:
- 写入时数据存放在内存树中,也叫memtable,与此同时,起辅助作用的数据结构(BloomFilter、稀疏索引)也会随之被更新。
- 当内存树达到预定大小,会按key有序持久化到磁盘。
- 查询时首先检查BloomFilter,如果BloomFilter中不存在则可直接返回,否则需要从新到旧依次遍历所有Segment。
- 查找每个Segment时,首先从稀疏索引中获取key所在范围起始值,然后在范围内查找key,一旦找到,立马返回。