层级压缩

文件结构

磁盘上的文件被组织在多个级别。我们称它们为一级、二级等,或者简称为L1、L2等。 一个特殊的0级(或简称L0)包含刚从内存写缓冲区(memtable)刷新的文件。 每一级(0级除外)为一次数据排序运行:

图片资源/五、14.1 压缩/图1.png

在每个层(0级除外)内,数据范围划分为多个SST文件:

图片资源/五、14.1 压缩/图2.png

该级别是有序运行的,因为每个SST文件中的键都是有序的(参见基于块的表格式作为示例)。为了确定键的位置,我们首先对所有文件的开始/结束键进行二叉搜索,以确定哪个文件可能包含键,然后在键内进行二叉搜索,以确定键的确切位置。总之,它是一个完整的二进制搜索,遍历该级别的所有键。

所有非0级别都有目标大小。压缩的目标将是限制目标下那些级别的数据大小。规模目标通常呈指数增长:

图片资源/五、14.1 压缩/图3.png

压缩

当L0文件数量达到level0_file_num_compaction_trigger时,L0的文件将被合并到L1中。 通常情况下,我们必须拾起所有的L0文件,因为它们通常是重叠的:

图片资源/五、14.1 压缩/图4.png

压缩完成后,可能会使L1的尺寸超过目标:

图片资源/五、14.1 压缩/图5.png

在本例中,我们将选择至少一个文件,并将其与L2的重叠范围合并。结果文件将放在L2:

图片资源/五、14.1 压缩/图6.png

如果结果推动下一层的大小超过目标,我们做同样的事情——拿起一个文件并合并到下一层:

图片资源/五、14.1 压缩/图7.png

然后

图片资源/五、14.1 压缩/图8.png

然后

图片资源/五、14.1 压缩/图9.png

如有需要,多个压缩可并行执行:

图片资源/五、14.1 压缩/图10.png

允许的最大压缩数由max_background_compactions控制。

但是,L0到L1压缩不能并行化。在某些情况下,它可能成为限制总压缩速度的瓶颈。在这种情况下,用户可以将max_subcompactions设置为大于1。在这种情况下,我们将尝试分区范围,并使用多个线程来执行它:

图片资源/五、14.1 压缩/图11.png

选择压缩

当多个级别触发压缩条件时,RocksDB需要首先选择要压缩的级别。每个级别都会生成一个分数:

  • 对于非零级别,分数是级别的总大小除以目标大小。如果已经选择了正在压缩到下一个级别的文件,那么这些文件的大小不包括在总大小中,因为它们很快就会消失。

  • 对于级别0,分数是文件总数,除以level0_file_num_compaction_trigger,或者max_bytes_for_level_base上的总大小,后者更大。(如果文件大小小于level0_file_num_compaction_trigger,则无论得分有多大,压缩都不会从0级触发)

我们比较了各个级别的得分,得分最高的级别优先压缩

在”选择级别压缩文件”中解释了从级别压缩哪些文件。

层级’s目标大小

level_compaction_dynamic_level_bytes是false

如果level_compaction_dynamic_level_bytes为false,则确定级别目标如下:L1的目标将是max_bytes_for_level_base。然后Target_Size(Ln+1) = Target_Size(Ln) max_bytes_for_level_multiplier max_bytes_for_level_multiplier_extra [n]。max_bytes_for_level_multiplier_extra默认为all 1。

例如,如果max_bytes_for_level_base = 16384, max_bytes_for_level_multiplier = 10, max_bytes_for_level_multiplier_extra未设置,则L1、L2、L3和L4的大小分别为16384、163840、1638400和16384000。

level_compaction_dynamic_level_bytes是true

最后一层的目标大小(num_levels-1)总是该层的实际大小。然后Target_Size(Ln-1) = Target_Size(Ln) / max_bytes_for_level_multiplier。我们不会填充目标低于max_bytes_for_level_base / max_bytes_for_level_multiplier的任何级别。这些级别将保持为空,所有L0压缩将跳过这些级别,直接进入具有有效目标大小的第一个级别。

例如,如果max_bytes_for_level_base为1GB, num_levels=6,最后一层的实际大小为276GB,那么L1-L6的目标大小将分别为0,0,0.276GB, 2.76GB, 27.6GB和276GB。

这是为了保证一个稳定的lsm树结构,如果level_compaction_dynamic_level_bytes为false,则无法保证该结构的稳定。例如,在前面的例子中:

图片资源/五、14.1 压缩/图12.png

我们可以保证90%的数据存储在最后一层,9%的数据存储在第二层。这样做有很多好处。

TTL

如果文件的键范围内的数据没有更新,则文件可以存在于LSM树中,而不需要经过长时间的压缩过程。 例如,在某些用例中,键是”软删除”的 `) ——将值设置为空,而不是实际发出删除。 对这个”已删除”键范围可能不再有任何写操作,如果有,这些数据可能会在LSM中保留很长时间,从而导致空间浪费。

为了解决这一问题,引入了动态ttl列族选项。当没有其他后台工作时,比TTL更老的文件(以及数据)将被安排进行压缩。 这将使数据经过常规压缩过程,并删除旧的不需要的数据。这还有一个(好的)副作用,非底层的所有数据都比ttl新,底层的所有数据都比ttl老。 注意,这可能导致更多的写操作,因为RocksDB会安排更多的压缩。