感谢作者,原文链接:https://zhuanlan.zhihu.com/p/348514713
Leveled compaction in RocksDB 是一种混合实现,从纯粹概念上讲,混合了tiered compaction + leveled compaction.
在memtable层面,RocksDB有选项max_write_buffer_number(这个特性叫做pipelining)。这个选项意味着,在某一个时间点,会有一个writebuffer被写入,但是,同时会存在一些之前写入,但是现在已经变成不可变的writebuffer.这些不可变的writebuffer会在后续被flush到L0(在flush写盘的过程中,会做dedup的工作,这个特性叫garbage collection, 前提是min_write_buffer_number_to_merge>1)。
在memtable层面是不存在compaction过程的。从memtable flush到L0是一个compaction过程,这个过程类似于tiered compaction.这个过程只是新增一个SST文件,不需要读取/重写现有的L0文件。对于L0,有配置选项level0_file_num_compaction_trigger,这意味着,在L0层,可以有N个文件(每个文件是一个sorted run),超过这个文件数目后,会出发compaction. 如果这个选项是-1,那么,L0的compaction不会因为文件数目的增加而被触发(会被其他的触发,比如说,文件的总大小, 选项max_bytes_for_level_base)
从L1到Lmax,每层都只有一个sorted run, 每层包括很多SST文件,按照range切分。每层有一个targetsize,在compaction时,系统希望把每层的文件总大小控制在target_size内。一般而言,这个target_size在层层指数增长。
从L0向L1做compaction时,一般而言,会需要把L0的文件全部扫一遍,因为L0的文件由多个sorted run组成,key会有重叠。这会限制多线程的使用,但是,有一个概念叫做subcompaction,大体而言,是说会尝试对所有文件根据key range做partition,然后就能用上多线程,可以提高性能(前提是选项max_subcompactions>1)
当L0向L1做完compaction后,L1的文件总大小会变大,有可能导致L1的target_size被突破,出发L1向L2的compaction.一般而言,会从L1的文件当中挑选一个或多个(挑选的原则有选项compaction_pri决定,目前的默认是kMinOverlappingRatio,之所以会挑选多个,本质是一种优化,及捎带上另外一些文件不会增加compaction到下一层需要重写的文件数目), 然后把他们compaction到L2的文件中。整个连锁反应中,应为涉及很多层,同层间也可以选互不干扰的文件,所以可以使用多线程compaction。当然,多线程的限制可以由选项max_background_compactions控制。
Target_size的计算
L1的target_size=max_bytes_for_level_base,level之间的倍数为max_bytes_for_level_multiplier.静态而言,整个计算很简单,但是,在真实运行中, 可能存在低层级的数据多,高层级的数据少,这对性能不利,所以,rocksdb提供了动态计算target_size的功能(选项level_compaction_dynamic_levelbytes=true).当max_bytes_for_level_multiplier=10,当前num_levels=6,此时最后一层确有数据276G的情况下,各层的target_size是根据最高层按比例倒推的,L1-L6 分别是 0, 0, 0.276GB, 2.76GB, 27.6GB and 276GB.如此可以确保90%的数据在最后一层,%9的数据在倒数第二层,一次类推。
在写高峰,可能出现一种情况,就是L0的文件很多,然后L0向L1的compaction工作不过来,导致L0文件积压。在这种情况下,如果不采取措施,会导致读性能恶化,为了缓解这种情况,rocksdb会选择在L0层内做compaction,把小文件合并成大文件。
另一方面,rocksdb会选择动态调整各个level的target_size(而不再遵循用户配置中的定义)。举例说明,用户的配置是放大系数10,max_bytes_for_levelbase=1G.那么从L1到L4的targetsize分别是1G,10G,100G,1000G。假设此时实际的各层大小是L1到L4为0.64G,6.4G,64G,640G. 当前状态是一个相对理想和稳定的状态。如果此时写突增,L0积压到10G, 可以想象,如果按照用户的配置,L2到L3的compaction会被触发执行,但是这并不能很好的解决写积压的问题,现在的核心问题是用更多的资源做L0 -> L1 & L1 -> L2的compaction,而不应该浪费资源做L2 -> L3的compaction.所以rocksdb会选择调整target_size,他把L1的targetsize设置成等于10G, 最后一层设置成640G,中间指数分布,最终的结果是L1到L4分别为10G,40G,160G,640G,如此以来,L2->L3的compaction可以被避免。
当然,以上这些问题并不能完全解决写突发的问题,只是缓解,相关工作还在进行中。
网友评论