LSM-Tree
适用于磁盘存储的索引结构
- 原地存储(B+Tree):读性能更好,多应用于关系型数据库。
- 非原地存储(LSM-Tree):写性能更好,多应用于非关系型数据库。
什么是非原地存储
写入是有序的,但数据是无序的,假设一组操作如下。
write("hello")
write("level")
write("db")
数组组织格式如下。
"hello" | "level" | "db" |
---|
这样的组织格式让写入性能更好,读取性能更差,需要遍历查询。
对于键值存储也有会空间浪费的情况。
write["x"] = 1
write["a"] = 1
write["x"] = 0
数据组织格式如下。
"x" = 1 | "a" = 1 | "x" = 0 |
---|
键值的覆盖需要多份键空间,读取需要自后向前遍历。
如何解决空间冗余的问题
随着数据不断写入,过多的冗余空间会导致存储空间不足,因此需要对存储记录进行压缩。
设置一个分段大小,当前分段大小达到上限后,新创建一个分段为当前分段。数据写入当前分段,压缩之前的分段,压缩后每个记录分段内没有重复的键。
范围查询如何进行
可以对每个分段排序,再对所有分段做归并排序,归并的过程中删除不同分段间重复的键。
但在磁盘上排序并不是个高效的事情,因此记录应当存储于内存之中。当前分段为memtable,相应的之前的分段为immutable memtable。
换成内存存储后的改进
内存的随机写入和顺序写入性能是一样好的,因此没必要顺序写入memtable再进行排序,可以适用skiplist作为memtable的数据结构。
内存是有限的,不能无穷尽的生成immutable memtable,因此每生成一个,就持久化到磁盘上,这样在内存中就只要一个memtable和一个immutable memtable就够了。
预写日志保证数据不丢失
内存是易失存储,memtable中的数据再未持久化到磁盘前有丢失的风险,因此数据在写入memtable前,需要先写到log中,log在磁盘上,所以顺序写。log不用做存储,只用作备份。
log有两个,分别对应memtable和immutable memtable,当immutable memtable持久化到磁盘后,对应log中的记录就会被删除。
持久化数据的组织方式
持久化到磁盘上的数据称为SST(Sorted String Table)文件,从内存直接dump到磁盘的文件,属于Level 0的文件,Level 0的大小是有限的,在若干个Level 0 SST文件生成后,占用空间达到了Level 0的上限,就会把Level 0归并为若干个文件,归并的过程中,保证归并后的文件间不存在重复的键。
由Level 0归并的文件,属于Level 1层的文件,相应的Level 1层空间达到上限后,会继续向下归并产生Level 2、Level 3 ...除了Level 0外,其余所有同Level的SST间不存在重复的键。