目录

1. 整体架构

       文件类型

       文件组织结构

2. Flush

3. Compaction

4. Write Stall


        RocksDB是facebook开发的一款高性能的kv数据库,源自于LevelDB,并且针对闪存进行了良好的优化。

        RocksDB内部基于LSM tree的结构,LSM(Log-structured Merge Tree,日志结构化的合并书),核心在于写入时先将数据缓存在内存中,然后定时刷入底层存储,这样做的优点是提供很快的写入速度,因为将大量的随机写变成了批量的顺序写。

        简单介绍一下RocksDB的文件类型、总体结构和写流程等。下文中图片大多源自RocksDB的官方文档,想了解可以自行翻阅。

RocksDB Wiki - 《RocksDB Document(20191008)》

1. 整体架构

       文件类型

        RocksDB的文件类型主要有MemTable,SST,WAL(写前日志,对应log文件),此外还有MANIFEST文件存储元信息,Current文件指向当前版本,以及OPTION文件,LOCK,IDENTITY文件等等。本文主要介绍RocksDB的compaction和flush,以及涉及到的MemTable,SST文件。

Rocksdb java 使用 rocksdb compaction_Rocksdb java 使用

       文件组织结构

        RocksDB的文件整体结构分为内存和外存两部分,MemTable存储在内存中,SST和WAL在外存中。MemTable和SST类似,都是存储kv数据的内部有序文件,是存储数据的基本单位,RocksDB中大小默认是64MB。

Rocksdb java 使用 rocksdb compaction_bc_02

        内存有几个MemTables接受和存储KV对。 当一个活跃MemTable被填满时,它变成一个immutable MemTable,新的kv将被写入另一个MemTable。而写满了的MenTable将被刷入外存中成为SST,这个过程称为Flush。

        外存中的SSTs被划分在了不同的level:L0, L1, L2......Ln,level的大小从上(L0)到下(Ln)成倍数增长,即Ln+1的大小是Ln的x倍,RocksDB中默认为10x。除了L0之外,一个level都是整体有序的,无论是SST内部还是相邻SST之间。而L0中,不同的SST之间的key值范围是可以重合的。L0接受从内存Flush下来的文件,而Compaction使用归并排序的方式将数据往下层放,从而保持LSM树的结构稳定。

Rocksdb java 使用 rocksdb compaction_bc_03

        Rocksdb的Flush和Compaction都通过后台线程触发,Rocksdb通过线程池管理这些后台线程,通常flush的线程池优先级高于compaction。参数max_background_jobs、max_background_compactions、max_background_flushes用来限制这些后台线程的最大数量。

2. Flush

        写操作不会造成立即的数据持久化,而是用内存吸收了写入的数据,当内存中的数据到达一定量后,再将它们一次性下刷到持久性存储中。

一个memtable的大小超过write_buffer_size(默认64MB)

        满足触发条件时,将调用优先级为HIGH的后台线程来进行新创建的flush工作,将一个imutable写入L0中。RocksDB不用对这个MemTable做过多调整,因为L0中的文件之间是无序的,可以将MemTable中的kv稍做整理,去掉无效kv后直接写入L0中。

3. Compaction

        Compaction操作的大体过程是在Lk中选取SST,然后再选取Lk+1中有重叠的SST文件,使用归并排序合并到一起后再放入Lk+1中。由于L0的特殊性,Compaction也分为两种:L0和非L0.

(1)Level 0

        触发条件:文件个数到达level0_file_num_compaction_trigger,默认为4。

        压缩方式:选取所有key范围重合的文件,合并到L1,一般会选取所有文件,因为L0无序。

Rocksdb java 使用 rocksdb compaction_bc_04

 L0 Compaction

(2)Level n,n>1

        触发条件:文件总大小到达MaxBytesForLevel(level最大阈值)。阈值选取分为静态值预设和动态调整两种方式,动态调整时,RocksDB会算出所有文件大小总和,然后再计算各层相应的大小。

        压缩方式:选取至少一个SST,一般从最大的选起,然后在L(n+1)中选取key值范围重合的sst文件,并合并到L(n+1)中。 Note:1. 触发条件中的总大小不包含已经决定要合并到下层的文件大小,因为它马上就不在这一层了;2. 选取L(n+1)中的文件时,注意不能有正在压缩的文件,这是compaction能并行的基本条件。

Rocksdb java 使用 rocksdb compaction_数据库_05

       Ln Compaction        

        那么如果有多个level超过限制怎么办?RocksDB通过计算score,即通过文件大小(或个数)除以阈值,获得比例值。score值越大,优先级越高。

        subcompaction:RocksDB中Flush和Compaction允许并行,从而加快了压缩速率,然而由于L0的Compaction通常选取了所有SST,导致不能同时进行多个Compaction线程,因此L0的压缩速率可能成为系统瓶颈。因此,RocksDB从单个compaction内部并行的角度设计了subcompaction机制。

Rocksdb java 使用 rocksdb compaction_database_06

   subcompaction示意

         当使用subcompaction时,一个compaction线程的工作函数,会将key range分为若干份,每个sub-compaction处理其中一份,并创建相应的子线程来运行。具体是如何做切割的暂时没有研究。在一般情况下,subcompaction开关是关闭的,因为它是为了解决L0 compaction太慢的问题而提出的方案。

4. Write Stall

        根据RocksDB官方给出的文档, 当rocksdb认定磁盘压缩的速度跟不上写入速度时,会主动减慢write速率,甚至停止。Performance - Write Stalls - 《RocksDB Document(20191008)》

        触发条件:

  1. 内存写满:memtable个数达到max_write_buffer_number。
  2. L0文件个数达到level0_slowdown_writes_trigger/ level0_stop_writes_trigger
  3. pending_compaction_bytes(等待compaction的文件总大小)到达soft_pending_compaction_bytes/ hard_pending_compaction_bytes