LSM-Tree
DDIA中提到了LSM-Tree,是一种高效的存储引擎,被广泛应用于分布式存储系统中。简单画图解释下相关的设计。
Append-Only Log⌗
如果我们要存储数据,最简单的可能会选择key-value的存储方式。那么内存中就会有一个Map
,key是数据的key,value是数据的value。但是这种方式有一个问题,如果内存中的数据丢失了,那么数据就丢失了。所以我们需要将数据持久化到磁盘上。此时,内存中的value就需要改为指向磁盘上的数据的位置。
此时我们可以选择是哟个Append-Only Log
,也就是只追加的日志。每次写入数据,都会追加到日志的末尾。这样即使系统崩溃了,我们也可以通过日志来恢复数据。
写入流程
- 将数据写入内存中的Map
- 将数据通过append的方式写入日志
读取流程
- 从内存中的Map中读取要查找的数据在文件中的位置
- 从日志中读取数据
Pros
- 简单
- 可以通过日志来恢复数据
- 顺序写入,性能高
Cons
- 需要一个内存中的Map来存储数据,如果数据量大,内存不够,这种方案就不适用了
- 无法进行范围查询,因为key是无序的
LSM-Tree⌗
为了解决Append-Only Log
的问题。我们可以引入LSM-Tree
。`
内存中的数据使用Tree的数据结构进行存储,这样可以进行范围查询。当内存中的数据达到一定的阈值时,该tree变为不可写状态,使用一个新的tree接收最新的写数据,将不可变的tree数据写入到磁盘上。此时磁盘上的数据也是有序的。该tree称为MemTable
,磁盘上的数据文件称为SSTable
。
为了保证memtable在crash的时候不会丢失数据,我们可以使用WAL
,也就是Write-Ahead Logging
。在写入memtable之前,先将数据写入到WAL中。这样即使系统崩溃了,我们也可以通过WAL来恢复数据。
写入流程
- 将数据写入WAL,再写入MemTable
- 当内存中的Tree达到一定的阈值时,将Tree写入磁盘上的SSTable
读取流程
- 从内存中的Tree中读取数据
- 如果内存中的Tree中没有数据,从磁盘上的SSTable中读取数据
- SSTable从最新的文件开始读取,因为SSTable的数据是有序的,所以我们可以使用二分查找来查找数据。但是不同文件之间未必是有序的。
- 还可以通过Bloom Filter来判断数据是否存在于SSTable中,减少无效查询
如果SSTable中的数据量太大,我们可以将多个SSTable合并成一个更大的SSTable。清理掉重复的数据,保留最新的数据。
Compaction & Write Amplification⌗
LSM-Tree中的数据可能会有很多重复的数据,为了减少重复的数据,我们可以将多个SSTable合并成一个更大的SSTable。这个过程称为Compaction
。
Write Amplification
: 多个SSTable合并成一个SSTable,写入的数据量会增加。这个过程称为Write Amplification
。对于用户来说,我们只是写入了一次数据,但是因为文件合并,实际写入的数据量会增加。
为了缓解这个问题,我们可以使用LevelDB
中的Level
的概念。将SSTable分为不同的Level,每个Level的SSTable的大小是固定的。当一个Level的SSTable达到一定的阈值时,将多个SSTable合并成一个更大的SSTable。这样可以减少Write Amplification。
- 如果
memtable
写满了,将memtable
写入到Level 0的SSTable中。 - 当Level 0的SSTable达到一定的阈值时,将多个SSTable合并成一个更大的SSTable,写入到Level 1的SSTable中。
- 以此类推,直到最后一个Level,这个Level的SSTable的大小是固定的。
- 从level1到leveln, 每个层级内的SSTable文件之间通常是有序的,但是不同层级之间的SSTable文件之间未必是有序的。