概述html
compaction主要包括两类:将内存中imutable 转储到磁盘上sst的过程称之为flush或者minor compaction;磁盘上的sst文件从低层向高层转储的过程称之为compaction或者是major compaction。对于myrocks来讲,compaction过程都由后台线程触发,对于minor compaction和major compaction分别对应一组线程,经过参数rocksdb_max_background_flushes和rocksdb_max_background_compactions能够来控制。经过minor compaction,内存中的数据不断地写入的磁盘,保证有足够的内存来应对新的写入;而经过major compaction,多层之间的SST文件的重复数据和无用的数据能够迅速减小,进而减小sst文件占用的磁盘空间。对于读而言,因为须要访问的sst文件变少了,也会有性能的提高。因为compaction过程在后台不断地作,单位时间内compaction的内容很少,不会影响总体的性能,固然这个能够根据实际的场景对参数进行调整,compaction的总体架构能够参见图1。了解了compaction的基本概念,下面会详细介绍compaction的流程,主要包括两部分flush(minor compaction),compaction(major compaction),对应的入口函数分别是BackgroundFlush和BackgroundCompaction。linux
图1git
flush(minor-compaction)github
Rockdb中在内存的数据都是经过memtable存储,主要包括两种形式,active-memtable和immutable-memtable。active-memtable是当前正在提供写操做的memtable,当active-memtable写入超过阀值(经过参数wirte_buffer_size控制),会将这个memtable标记为read-only,而后再建立一个新的memtable供新的写入,这个read-only的memtable就是immutable-memtable。咱们所说的flush操做就是将imumutable-memtable 写入到level0的过程。flush过程以column family为单位进行,一个column family是一组sst文件的集合,在myrocks中一个表能够是一个单独的column family,也能够多个表共用一个column family。每一个column family中可能包含一个或多个immutable-memtable,一个flush线程会抓取column family中全部的immutable-memtable进行merge,而后flush到level0。因为一个线程在flush过程当中,新的写入也源源不断进来,进而产生新的immutable-memtable,其它flush线程能够新起一个任务进行flush,所以在rocksdb体系下,active-memtable->immutable-memtable->sst文件转换过程是流水做业,而且flush能够并发执行,相对于levelDB,并发compaction的速度要快不少。经过参数max_write_buffer_number能够控制memtable的总数量,若是写入很是快,而compaction很慢,会致使memtable数量超过阀值,致使write stall的严重后果。另一个参数是min_write_buffer_number_to_merge,整个参数是控制至少几个immutable才会触发flush,默认是1。flush的基本流程以下:算法
1.遍历immutable-list,若是没有其它线程flush,则加入队列数据结构
2.经过迭代器逐一扫描key-value,将key-value写入到data-block 架构
3.若是data block大小已经超过block_size(好比16k),或者已经key-value对是最后的一对,则触发一次block-flush并发
4.根据压缩算法对block进行压缩,并生成对应的index block记录(begin_key, last_key, offset)app
5.至此若干个block已经写入文件,并为每一个block生成了indexblock记录ide
6.写入index block,meta block,metaindex block以及footer信息到文件尾
7.将变化sst文件的元信息写入manifest文件
flush实质是对memtable中的记录进行一次有序遍历,在这个过程当中会去掉一些冗余的记录,而后以block为单位写入sst文件,写入文件时根据压缩策略肯定是否对block进行压缩。为何会有冗余记录?这个主要是由于rocksdb中不管是insert,update仍是delete,全部的写入操做都是以append的方式写入memtable,好比前后对key=1的记录执行三个操做insert(1),update(1),delete(1),在rocksdb中会产生3条不一样记录。(在innodb中,对于同一个key的操做都是原地更新,只有一条记录)。实际上delete后这个记录不该该存在了,因此在合并时,能够干掉这些冗余的记录,好比这里的insert(1),update(1),这种合并使得flush到level0的sst已经比较紧凑。冗余记录主要有如下三种状况:(user_key, op)表示对user_key的操做,好比put,delete等。
1.对于(user_key,put),(user_key,delete),则能够将put删掉
2.对于(user_key,single-delete),(user_key,put),single-delete保证put,delete成对出现,能够同时将两条记录都删掉。
3.对于(user_key,put1),(user_key,put2),(user_key,put3)能够干掉比较老的put
对于以上3种状况,都要考虑snapshot,若是要删除的key在某个snapshot可见,则不能删除。注意第1种状况,(user_key,delete)这条记录是不能被删除的,由于对用户而言,这条记录已经不存在了,但因为rocksdb的LSM-tree存储结构,这个user_key的记录可能在level0,level1或者levelN,因此(user_key, delete)这条记录要保留,直到进行最后一层的compaction操做时才能将它干掉。第2种状况,single-delete是一个特殊的delete操做,这个操做保证了put,delete必定是成对出现的,因此flush时,能够将这两条记录同时干掉。
compaction(major-compaction)
咱们一般所说的compaction就是major-compaction,sst文件从低level合并到高level的过程,这个过程与flush过程相似,也是经过迭代器将多个sst文件的key进行merge,遍历key而后建立sst文件。flush的触发条件是immutable memtable的数量是否超过了min_write_buffer_number_to_merge,而compaction的触发条件是两类:文件个数和文件大小。对于level0,触发条件是sst文件个数,经过参数level0_file_num_compaction_trigger控制,score经过sst文件数目与level0_file_num_compaction_trigger的比值获得。level1-levelN触发条件是sst文件的大小,经过参数max_bytes_for_level_base和max_bytes_for_level_multiplier来控制每一层最大的容量,score是本层当前的总容量与能存放的最大容量的比值。rocksdb中经过一个任务队列维护compaction任务流,经过判断某个level是否知足compaction条件来加入队列,而后从队列中获取任务来进行compact。compaction的主要流程以下:
1.首先找score最高的level,若是level的score>1,则选择从这个level进行compaction
2.根据必定的策略,从level中选择一个sst文件进行compact,对于level0,因为sst文件之间(minkey,maxkey)有重叠,因此可能有多个。
3.从level中选出的文件,咱们能计算出(minkey,maxkey)
4.从level+1中选出与(minkey,maxkey)有重叠的sst文件
5.多个sst文件进行归并排序,合并写出到sst文件
6.根据压缩策略,对写出的sst文件进行压缩
7.合并结束后,利用VersionEdit更新VersionSet,更新统计信息
上面的步骤基本介绍了compaction的流程,简单来讲就是选择某个level的sst文件与level+1中存在重叠的sst文件进行合并,而后将合并后的文件写入到level+1层的过程。经过判断每一个level的score是否大于1,肯定level是否须要compact;对于level中sst文件的选择,会有几种策略,默认是选择文件size较大,包含delete记录较多的sst文件,这种文件尽快合并有利于缩小空间。关于选择sst文件的策略能够参考options.h中的CompactionPri的定义。每次会从level中选取一个sst文件与下层compact,但因为level0中可能会有多个sst文件存在重叠的范围,所以一次compaction可能有多个level0的sst文件参与。rocksdb后台通常有多个线程执行compact任务,compaction线程不断地从任务队列中获取任务,也会不断地检查每一个level是否须要compact,而后加入到队列,所以总体来看,compact过程是并发的,但并发的基本原则是,多个并发任务不会有重叠的key。对于level0来讲,因为多个sst文件会存在重叠的key范围,根据level0,level+1中参与compact的sst文件key范围进行分区,划分为多个子任务进行compact,全部子任务并发执行,都执行完成后,整个compact过程结束。另外还有一个问题要说明的是,compact时并非都须要合并,若是level中的输入sst文件与level+1中无重叠,则能够直接将文件移到level+1中。
Universal Compaction
前面介绍的compaction类型是level compaction,在rocksdb中还有一类compaction,称之为Univeral Compaction。Univeral模式中,全部的sst文件均可能存在重叠的key范围。对于R1,R2,R3,...,Rn,每一个R是一个sst文件,R1中包含了最新的数据,而Rn包含了最老的数据。合并的前提条件是sst文件数目大于level0_file_num_compaction_trigger,若是没有达到这个阀值,则不会触发合并。在知足前置条件的状况下,按优先级顺序触发如下合并。
1.若是空间放大超过必定的比例,则全部sst进行一次compaction,所谓的full compaction,经过参数max_size_amplification_percent控制。
2.若是前size(R1)小于size(R2)在必定比例,默认1%,则与R1与R2一块儿进行compaction,若是(R1+R2)*(100+ratio)%100<R3,则将R3也加入到compaction任务中,依次顺序加入sst文件
3.若是第1和第2种状况都没有compaction,则强制选择前N个文件进行合并。
相对于level compaction,Univeral compaction因为每一次合并的文件较多,相对于level compaction的多层合并,写放大较小,付出的代价是空间放大较大。除了前面介绍的level compaction和univeral compaction,rocksdb还支持一种FIFO的compaction。FIFO顾名思义就是先进先出,这种模式周期性地删除旧数据。在FIFO模式下,全部文件都在level0,当sst文件总大小超过阀值max_table_files_size,则删除最老的sst文件。整个compaction是LSM-tree数据结构的核心,也是rocksDB的核心,本文梳理了几种compaction方式的基本流程,里面还有不少的细节没有涉及到,有兴趣的同窗能够在本文的基础上仔细阅读源码,加深对compaction的理解。
附录
相关文件:
rocksdb/db/flush_job.cc
include/rocksdb/universal_compaction.h
rocksdb/db/compaction_job.cc
db/compaction_picker.cc
rocksdb/table/block_based_table_builder.cc
相关接口:
FlushMemTableToOutputFile //flush memtable到level0
FlushJob::Run //flush memtable 任务
PickMemtablesToFlush //选择能够flush的immutable-memtable
WriteLevel0Table //刷sst文件到level0
BuildTable //实现建立sst文件
UniversalCompactionPicker::NeedsCompaction //是否须要compact
PickCompaction //须要进行compact的sst文件
PickCompactionUniversalReadAmp //选择相邻的sst文件进行合并
NeedsCompaction //判断文件是否level是否须要compact
LevelCompactionPicker::PickCompaction // 获取level中sst文件进行compact
LevelCompactionPicker::PickCompactionBySize
IsTrivialMove // 是否能够移动更深的Level,没有overlap的状况下。
ShouldFormSubcompactions // 判断是否能够将compaction任务分片
CompactionJob::Prepare // 划分子任务
CompactionJob::Run() // compaction的具体实现
BlockBasedTableBuilder::Finish //生成sst文件
参考文档
http://rocksdb.org/blog/2016/01/29/compaction_pri.html
http://smalldatum.blogspot.com/2016/02/compaction-priority-in-rocksdb.html
http://rocksdb.org/blog/2016/01/29/compaction_pri.html
https://github.com/facebook/rocksdb/blob/v3.11/include/rocksdb/options.h#L366-L423
http://rocksdb.org/blog/2015/07/23/dynamic-level.html
https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide
http://alinuxer.sinaapp.com/?p=400
http://dirtysalt.github.io/leveldb.html
http://openinx.github.io/2014/08/17/leveldb-compaction/