LSM-Tree树存储架构
日志结构合并树(Log-Structured Merge Tree),不是严格的树,有不同的实现。
LevelDB的LSM-Tree 由以下几部分组成:
-
日志文件:防止故障导致内存表数据丢失。
-
内存表:
- 唯一可写的Memtable。当Memtable大小到达上限时,将转变为只读的 Immutable Memtable,然后创建一个新的 Memtable 供数据写入。
- 一个或多个的只读的Immutable Memtable。Immutable Memtable将排序写入磁盘L0层,成为SSTable文件。
内存表的数据结构往往是跳表(Skip List),支持高效的数据插入和查找。为了支持按序扫描操作,Leveldb的Memtable中的键值对是根据Key的大小有序存储的。
为了避免多次的申请和释放(new/delete)带来的开销,可以一次申请大块的内存,多次分给客户,实现用户区的内存管理和分配,即内存池
-
静态表:
磁盘中包含多个 level 的只读的 SSTable静态表文件。
Level 越小,表示该层级的 SSTable 数据越新,level 越大,表示该层级的 SSTable 数据越旧。
SSTable包括:
-
数据存储区:存放实际的 key-value 数据,由数据块组成。
对于数据块:
-
L1层及以上的SSTable的数据块中的key是有序存储的,因此有很大的概率key的前缀重复出现,因此存储时采用前缀压缩,后一个key只存储与前一个key不同的部分。
-
重启点(restart)指出的位置就表示该key不按前缀压缩,而是完整存储该key。
重启点的第二个作用就是加速读取,可以通过二分的方法来定位具体的重启点位置。
-
-
数据管理区:存放元数据块MetaBlock,保存元信息,如索引(指向记录的偏移量)、布隆过滤器、统计信息等,目的是更快速便捷的查找SSTable中相应的记录。
- 索引块:记录每个数据块的最大Key与偏移,用于实现二分查找
SSTable文件合并和压缩操作(compaction) :
定期使用多路归并排序对多个文件中的所有记录重新进行排序,生成新的SSTable 文件,并丢弃已被覆盖或删除的值。
刚写入磁盘的L0层SSTable间是无序的,SSTable取值范围存在交集,L1层及以上SSTable间是有序的,SSTable 取值范围无交集。
如图所示,一个L层的SSTable 被并入L+1层,参与合并的L+1层SSTable 是与L层的SSTable取值范围存在交集的SSTable 。
-
读写操作:
-
写操作:
- 将记录以顺序写的方式追加到log日志文件末尾。
- 把记录写入到内存表 Memtable。
-
读操作:
-
按照Memtable、Immutable Memtable、 L0 SSTable、L1 SSTable ... LN SSTable顺序查找,即记录创建的时间先后顺序,先读到就是最新的。
利用L1层及以上SSTable间是有序的,SSTable 取值范围无交集,可使用二分查找快速确定此层需要查找的SSTable。
使用布隆过滤器可以快速确定目标数据是否可能在 SSTable 中,无需读取数据块。
-
优点和缺点:
-
优点:日志结构的写操作只需在日志文件和 Memtable 上各添加一条记录即可,磁盘文件合并是后台异步进行,写操作只有追加日志文件的磁盘顺序写,消除了随机写,写性能好。
-
缺点:
- 随机读性能一般,但根据局部性,最近写的数据更可能被读出,Memtable相当于缓存优化。
- 数据写入和数据合并会竞争磁盘I/O资源
- 对于区间查找,需要查找每个层级对应区间的SSTable ,读放大严重,层级一般不能太多。
Bitcask和LSM比较:
Bitcask和LSM都是日志结构的存储架构,消除了随机写,只会顺序写日志文件。
Bitcask实现更简单,但不支持区间查找。
LSM实现更复杂,支持区间查找。
Bitcask内存中的是索引,读写一定需要访问磁盘日志文件。
LSM的内存表中是数据,写操作是写内存表和磁盘日志文件,读操作是先读内存表,内存表实际上具有缓存功能,因此LSM一般比Bitcask更优。
Bitcask中的日志文件的功能是存储数据。
LSM中的日志文件的功能是保证数据的可靠性,数据存储在内存表和静态表中。