Tuning RocksDB - Options

在先前我们讨论了 RocksDB 的 statistics 和 write stall,但这些只能让我们发现问题,最终我们还是需要通过调整 RocksDB 的参数来提升性能。但 RocksDB 的参数以其数据多和复杂著称,要全部弄懂也要费一番功夫,这里也仅仅会说一下我们使用的一些参数,还有很多我们也需要后面慢慢去研究。

Parallelism

RocksDB 有两个后台线程,flush 和 compaction,两个都可以同时并行执行。在优先级上面,flush 是 HIGH,而 compaction 是 LOW,也就是 flush 的优先级会比 compaction 更高,这也很容易理解,如果数据都没有 memtable flush 到 level 0,后面也没法做 compaction。我们可以设置 flush 和 compaction 的最大线程数:

  • max_background_compaction:最大 compaction 线程数,默认是 1,但通常我们会调大,不然 compaction 会忙不过来。
  • max_background_flushes:最大 flush 线程数,默认是 1,在 TiKV 里面我们默认是 2,因为有多个 CF 可能会同时 flush。

General

  • filter_policy:也就是 bloom filter,通常在点查 Get 的时候我们需要快速判断这个 key 在 SST 文件里面是否存在,如果 bloom filter 已经确定不存在了,就可以过滤掉这个 SST,减少没必要的磁盘读取操作了。我们使用 rocksdb::NewBloomFilterPolicy(bits_per_key) 来创建 bloom filter,bits_per_key 默认是 10,表示可能会有 1% 的误判率,bits_per_key 越大,误判率越小,但也会占用更多的 memory 和 space amplification。
  • block_cache:为了加快从文件读取数据的速度,RocksDB 会将 block 缓存,虽然操作系统也有 OS cache,但通常,block cache 是缓存的没有被压缩的 block,而 OS cache 则是缓存的已经压缩好的 block。现在 RocksDB 也支持 direct IO 模式,这样就不会有 OS cache 了,但我们还没有使用过。另外,RocksDB 也支持一种 compressed block cache,类似 OS cache 的机制,但我们现阶段也没有使用过。另外,在 TiKV 里面,尤其是在内存比较小的机器上面,我们通常都会将 block 的 index 和 cache 也放在 cache 里面,防止 OOM。通常我们会使用 rocksdb::NewLRUCache(cache_capacity, shard_bits) 来创建一个 LRU cache。
  • max_open_files:RocksDB 会将打开的 SST 文件句柄缓存这,这样下次访问的时候就可以直接使用,而不需要重新在打开。当 缓存的文件句柄超过 max_open_files 之后,一些句柄就会被 close 掉。如果使用 -1,RocksDB 将一直缓存所有打开的句柄,但这个会造成比较大量的内存开销,尤其是在内存较小的机器上面,很容易造成 OOM。
  • block_size:RocksDB 会将一批 data 打包放到一个 block 里面,当需要访问某一个 key 的时候,RocksDB 会将整个 block 都 load 到内存里面。一个 SST 文件会包含很多个 block,每个 SST table 都包含一个 index 用来快速定位到对应的 block。如果 block_size 越大,那么一个 SST 文件里面 block 的个数就越少,这样 index 占用的 memory 和 space amplification 就越小,但这样就会增大 read amplification。

Flush

对于新插入的数据,RocksDB 会首先将其放到 memtable 里面,所以 RocksDB 的写入速度是很快的。当一个 memtable full 之后,RocksDB 就会将这个 memtable 变成 immutable 的,然后用另一个新的 memtable 来处理后续的写入,immutable 的 memtable 就等待被 flush 到 level 0。也就是同时,RocksDB 会有一个活跃的 memtable 和 0 或者多个 immutable memtable。对于 flush,我们需要关注:

  • write_buffer_size:memtable 的最大 size,如果超过了这个值,RocksDB 就会将其变成 immutable memtable,并在使用另一个新的 memtable。
  • max_write_buffer_number:最大 memtable 的个数,如果 active memtable full 了,并且 active memtable 加上 immutable memtable 的个数已经到了这个阀值,RocksDB 就会停止后续的写入。通常这都是写入太快但是 flush 不及时造成的。
  • min_write_buffer_number_to_merge:在 flush 到 level 0 之前,最少需要被 merge 的 memtable 个数。如果这个值是 2,那么当至少有两个 immutable 的 memtable 的时候,RocksDB 会将这两个 immutable memtable 先 merge,在 flush 到 level 0。预先 merge 能减小需要写入的 key 的数据,譬如一个 key 在不同的 memtable 里面都有修改,那么我们可以 merge 成一次修改。但这个值太大了会影响读取性能,因为 Get 会遍历所有的 memtable 来看这个 key 是否存在。

一个 Flush 的例子:

write_buffer_size = 512MB;
max_write_buffer_number = 5;
min_write_buffer_number_to_merge = 2;

假设我们的写入速率是 16MB/s,那么每 32s 的时间都会有一个新的 memtable 生成,每 64s 的时间就会有两个 memtable 开始 merge。取决于实际的数据,需要 flush 到 level 0 的大小可能在 512MB 和 1024MB 之间,一次 flush 也可能需要几秒的时间(取决于盘的顺序写入速度)。最多有 5 个 memtable,当达到这个阀值,RocksDB 就会组织后续的写入了。

Level Style Compaction

RocksDB 默认的将 SST 文件放在不同的 level,自然就是用的 level style compaction。Memtable 的被 flush 到 level 0,level 0 有最新的数据,其他更上层的 level 则是有老的数据。Level 0 里面的 SST 文件可能会有重叠,也就是不同的 SST 文件保护的数据 key range 会重叠,但 level 1 以及之上的 level 则不会重叠。对于一次 Get 操作来说,通常会在所有的 level 0 文件里面检查是否存在,但如果在其他层,如果在一个 SST 里面找到了这个 key,那么其他 SST 都不会包含这个 key。每一层都比上一层大 10 倍,当然这个是可以配置的。

一次 compaction 会将 level N 的一些文件跟 level N + 1 里面跟这些文件重叠的文件进行 compact 操作。两个不同的 compaction 操作会在不会的 level 或者不同的 key ranges 之间进行,所以可以同时并发的进行多个 compaction 操作。

在 level 0 和 level 1 之间的 compaction 比较 tricky,level 0 会覆盖所有的 key range,所以当 level 0 和 level 1 之间开始进行 compaction 的时候,所有的 level 1 的文件都会参与合并。这时候就不能处理 level 1 到 level 2 的 compaction,必须等到 level 0 到 level 1 的 compaction 完成,才能继续。如果 level 0 到 level 1 的速度比较慢,那么就可能导致整个系统大多数时候只有一个 compaction 在进行。

Level 0 到 level 1 的 compaction 是一个单线程的,也就意味着这个操作其实并不快,RocksDB 后续引入了一个 max_subcompactions,解决了 level 0 到 level 1 的 compaction 多线程问题,但现在 TiKV 还没有测试引入。通常,为了加速 level 0 到 level 1 的 compaction,我们会尽量保证level 0 和 level 1 有相同的 size。

当决定了 level 1 的大概 size,我们就需要决定 level multiplier。假设 level 1 的 size 是 512MB,level multiplier 是 10,整个 DB 的 size 是 500GB。Level 2 的 size 是 5GB,level 3 是 51GB,level 4 是 512GB,level 5 以及更上层的 level 就是空的。

那么 size amplification 就很容易计算了 (512 MB + 512 MB + 5GB + 51GB + 512GB) / (500GB) = 1.14,write amplification 的计算则是:任何一个 byte 首先写入 level 0,然后 compact 到 level 1,因为 level 1 的 size 跟 level 0 是一样的,所以 write amplification 在 level 0 到 level 1 的 compaction 是 2。当这个 byte compact 到 level 2 的时候,因为 level 2 比 level 1 大 10 倍,所以 write amplification 是 10。对于 level 2 到 level 3,level 3 到 level 4 也是一样。

所以总的 write amplification 就是 1 + 2 + 10 + 10 + 10 = 33。对于点查来说,通常会访问所有的 level 0 文件或者其他 level 的至多一个文件,这里我们可以使用 bloom filter 来减少 read amplification,但这个对于 range scans(也就是 iterator seek 这些)没啥作用,所以 range scans 的 read amplification 是 level 0 的文件数据 + 非空 level 的数量。

理解了上面的 level compaction 的流程,我们就可以开始配置相关的参数了。

  • level0_file_num_compaction_trigger:当 level 0 的文件数据达到这个值的时候,就开始进行 level 0 到 level 1 的 compaction。所以通常 level 0 的大小就是 write_buffer_size * min_write_buffer_number_to_merge * level0_file_num_compaction_trigger
  • max_bytes_for_level_basemax_bytes_for_level_multipliermax_bytes_for_level_base 就是 level1 的总大小,在上面提到,我们通常建议 level 1 跟 level 0 的 size 相当。上层的 level 的 size 每层都会比当前层大 max_bytes_for_level_multiplier 倍,这个值默认是 10,通常也不建议修改。
  • target_file_size_basetarget_file_size_multipliertarget_file_size_base 则是 level 1 SST 文件的 size。上面层的文件 size 都会比当前层大 target_file_size_multiplier 倍,默认 target_file_size_multiplier 是 1,也就是每层的 SST 文件都是一样的。增加 target_file_size_base 会减少整个 DB 的 size,这通常是一件好事情,也通常建议 target_file_size_base 等于 max_bytes_for_level_base / 10,也就是 level 1 会有 10 个 SST 文件。
  • compression_per_level:使用这个来设置不同 level 的压缩级别,通常 level 0 和 level 1 不压缩,更上层压缩。也可以对更上层的选择慢的压缩算法,这样压缩效率更高,而对下层的选择快的压缩算法。TiKV 默认全选择的 lz4 的压缩方式。
  • num_levels:预计的 level 的层数,如果实际的 DB level 超过了这么多层,也是安全的。默认是 7,实际也不会有超过这么多层的数据。

小结

上面只列举了一些 RocksDB 的调优参数,当然实际还远远不止这些,很多优化我们也在摸索阶段。对于不同的调优参数,后面也会继续解释。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,966评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,170评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,909评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,959评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,851评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,583评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,956评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,590评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,878评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,892评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,719评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,501评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,957评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,124评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,440评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,003评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,211评论 2 339

推荐阅读更多精彩内容

  • BlockBasedTable RocksDB用SST文件(Sorted Sequence Table)来存储用户...
    周肃阅读 4,877评论 0 2
  • RocksDB 使用 LSM 的方式用来提升写入的性能,但如果写入过快,超过了 RocksDB 处理的极限,Roc...
    siddontang阅读 4,057评论 3 6
  • 前面写了两篇文章介绍 LevelDB 的整体架构和接口使用。这篇文章,我们从代码的角度看看 LevelDB 的设计...
    linjinhe阅读 5,470评论 0 1
  • RocksDB 是一个性能非常强悍的 Key-Value 存储引擎,很多项目包括我们的 TiKV 都使用它来存储数...
    siddontang阅读 11,085评论 2 14
  • 一代伟人毛泽东不仅仅是一位气宇非凡的革命家,更是一位情感丰富的男人,这种伟大的情感在他的诗词中得到了充分的体现。 ...
    水乡醉客阅读 266评论 2 7