当前位置: 首页 > 工具软件 > RocksDB > 使用案例 >

rocksdb原理_rocksdb合并原理

印劲
2023-12-01

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的基本概念,下面会详细介绍compaction的流程,主要包括两部分flush(minor compaction),compaction(major compaction),对应的入口函数分别是BackgroundFlush和BackgroundCompaction。

flush(minor-compaction)

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)

5.至此若干个block已经写入文件,并为每个block生成了indexblock记录

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。

Level-0 层的文件在不停的从Memtable 中dump出来,那么何时才会把这些Level-0层的文件合并到Level-1 ?

RocksDB对对每一层进行打分,分数从0~1000000,这个分数的大小决定了进行Compact 的优先级,分数越大,越先进行Compact。

那么这个分数如何计算出来?

如果是Level-0层,会先算出当前有多少个没有进行Compact 的文件个数numfiles, 然后根据这个文件的个数进行判断,当numfiles<20 时,Score = numfiles/4;当24>numfiles>=20时,Score = 10000;当 numfiles>=24时,Score = 1000000:

相关参数

说明

level0_file_num_compaction_trigger

4

当有4个未进行Compact的文件时,达到触发Compact的条件

level0_slowdown_writes_trigger

20

当有20个未进行Compact的文件时,触发RocksDB,减慢写入速度

level0_stop_writes_trigger

24

当有24个未进行Compact的文件时,触发RocksDB停止写入文件,此时会尽快的Compact Level-0层文件

如果是Level-1+层,会去计算每一层未进行Compact文件的总Size,然后再和这一层的”容量值”做对比,得到一个比值,这个值就是该层的 CompactScore ,也就是说对于Level-1+层,Compact 触发条件是看这一层文件的大小而不是个数。Score = level_bytes / MaxBytesForLevel(level)

对于Level-1+层,每一层的最大Bytes 是如何计算出来的?

Level-1 层 文件总大小由 max_bytes_for_level_base 参数控制,而 Level-2 层的大小通过: Level_max_bytes[N] = Level_max_bytes[N-1] * max_bytes_for_level_multiplier^(N-1)*max_bytes_for_level_multiplier_additional[N-1] 计算得出:

参数

说明

max_bytes_for_level_base

10485760

用于指定Level-1 层总大小,超过这个值满足触发Compact条件

max_bytes_for_level_multiplier

10

每一层最大Bytes 乘法因子

max_bytes_for_level_multiplier_addtl[2]

1

Level-2 层总大小调整参数

max_bytes_for_level_multiplier_addtl[3]

1

Level-3 层总大小调整参数

max_bytes_for_level_multiplier_addtl[4]

1

Level-4 层总大小调整参数

max_bytes_for_level_multiplier_addtl[5]

1

Level-5 层总大小调整参数

max_bytes_for_level_multiplier_addtl[6]

1

Level-6 层总大小调整参数

在进行Compact的时候,会选择哪些文件进行Compact操作呢?

对于Level-0层文件,RocksDB总是选择所有的文件进行Compact操作,因为Level-0层的文件之间,可能会有key范围的重叠。

对于Level-N (N>1)层的文件,会先按照文件大小排序(冒泡排序),选出最大的文件,并计算这个文件Key 的起止范围,通过这个范围查找Level-N+1层文件,把选出的Level-N 文件和Level-N+1 文件做为输入,并且在Level-N+1新建一个或多个SST文件作为输出。

可以通过设置max_background_compactions 大于1 来使用并行Compact,不过这个并行Compact 不能作用到Level-0层。

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

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文件。

 类似资料: