LevelDB源码8:Compaction

技术 · 2022-07-08

入口函数

MaybeScheduleCompaction()

void DBImpl::BackgroundCompaction()

Compaction

  • Minor Compaction:Immutable Memtable向level-0中的SSTable文件的转换(优先级高)。
  • Major Compaction:将SSTable移到更高的Level去,需要保证Level 0以上的SSTable每层之间的键有序无重叠。Major Compaction其实就是一个归并排序的过程,对多个输入的SSTable,多路归并,输出多个连续的SSTable,代替原来的文件。根据Level 0的特殊性,可以分为两种类型:

    • Level 0 -> Level 1
    • Level n -> Level n + 1 (n > 0)

将多个小SSTable合并成一个大SSTable,可以解决查找的效率问题。但是,如果不断地有新的小SSTable进来,这些小SSTable都需要和这个大的SSTable进行合并,不管多大的SSTable来合并,都需要读取所有的磁盘数据,并且写入所有的磁盘数据。

大的SSTable是按键顺序存储的,可以将大SSTable进行分割,分割成多个小SSTable,每个SSTable都包含一段键的范围,而每个SSTable键的范围是不重复的,并且是按顺序排列的。这时候物理上存在多个SSTable文件,但是逻辑上依然是有一个大的SSTable。在查找的时候只需要多做一步,根据每个小的SSTable包含键的范围,可以做一个二分搜索,就可以找到实际的键在哪个小SSTable文件里。这样依然只需要读取一个SSTable,但是却使用了多个小文件代替一个大文件。这样的好处就是当有新SSTable需要合并到这个逻辑上的大SSTable时,只需要找到和新SSTable的键范围有重合的小SSTable的物理文件进行合并,这样就可以降低合并需要读取的数据量。

这样磁盘文件实际上分为两部分:一部分是MemTable直接写入的SSTable,另一部分是逻辑上的大SSTable。这实际上就是LevelDB里面的Level 0和Level 1。Level 0文件的键范围可能有重叠,而Level 1不会有重叠。而读取的时候,要读取Level 0和Level 1的数据,如果限制Level 0的文件数量,磁盘的读IO依然可以控制在一个常数范围内。

随着数据越写越多,Level 1越来越大,此时就算Level 0的SSTable很小,依然可能会和Level 1很多的SSTable文件重合,那么读写量依然很大。

这时候还需要改进,需要控制Level 1里所有文件的大小,那么多出的文件该怎么办呢?可以将它们再推到更高的一层Level 2中。Level 2里总文件大小设置为Level 1的10倍,在生成Level 1的SSTable时,控制大小使得最多与10个Level 2的SSTable重叠。如果需要将Level 1的一个SSTable合并到Level 2,需要读取的SSTable的数据量依然是一个常数级,是可控的。如果Level 2满了,可以将SSTable继续推向Level 3。因为每一Level的大小都是指数增长的,所以不需要几层,就可以放大量数据了。

这就是LevelDB名字的由来了,SSTable是分层存储的,Level 0的SSTable之间是重叠的,而Level 0以上的SSTable每层是有序不重叠的。SSTable在各Level之间移动的过程叫做Compaction,意思是让文件变得更加紧凑,易于查询。Level 0的SSTable会Compaction到Level 1,而Level 1的SSTable会Compaction到Level 2,随着Level的增大,每一层的文件总大小会以10倍增大,这样不但可以有大量的存储空间,而且每一次Compaction涉及的SSTable的数量都是可控的。Compaction实际上就是对输入的多个SSTable进行多路归并的过程。

随着Level的增多,读取更复杂了。要先读取MemTable,再读取Level 0的文件,Level 0可能有多个文件的键范围包括这个查找键,还需要读取Level 0以上的文件,每一Level最多有一个文件的键范围包括查找键。不过Level的数量有限,Level 0的文件数量也有限,所以需要读取的SSTable的数量依然是常数级,配合缓存、布隆过滤器等优化技术,可以提高读的性能。这是在读取性能和后台操作性能之间的折中,为了让写操作成为顺序写,而做的牺牲。

磁盘上有多个SSTable,需要知道每个SSTable属于哪一层,每一个SSTable的键范围,这些信息都存储在内存中。但是如果数据库重启了,就丢失了这些元信息,所以需要将它们持久化到磁盘。

在LSM-Tree中,除了level-0外,虽然每个level的SSTable间相互没有overlap,但是level与level间的SSTable是可以有overlap的。

每当LevelDB要查找key 18时,因为SSTable(k, i)的key范围覆盖了18,所以其每次都必须在该SSTable中seek,这一不必要的seek操作会导致性能下降。因此,在FileMetaData结构体中引入了allowed_seeks字段,该字段初始为文件大小与16KB的比值,不足100则取100;每次无效seek发生时LevelDB都会将该字段值减1。当某SSTable的allowed_seeks减为0时,会触发seek compaction,该SSTable会与下层部分SSTable合并。

合并的时候,需要找到下一层中与当前sstable有重合的sstable。

每次compaction后都新建一个版本。

minor compaction

Immutable Memtable向level-0中的SSTable文件的转换(优先级高)。

major compaction

major compaction 一个首要问题,就是要 compact 哪个文件。这个计算是在version完成的,与之相关的主要是两个属:

  • allowed_seeks(Seek Compaction):读取次数,只要这个块的范围包含了当前查找的关键字,但是又没有找到,说明是无效查找,经过allowed_seeks次后,这个sstable就需要被合并。
  • compaction_score\_(Size Compaction):文件大小。由Finalize(v)计算得到Size Compaction的最佳level和比率score。
LevelDB
Theme Jasmine