Tuning|Tuning RocksDB - Prefix Extractor

在 TiKV 里面,我们在 Write 和 Raft column family (CF) 上面使用了 prefix extractor 机制,用来加速数据的读取和插入插入。
通常我们直接将 key 插入到 RocksDB 里面,不做任何改变,所有的 key 是按照字节序依依次排列的,Bloom filter 里面也是对整个 key 的判断。但有些时候,这些机制并不适合。
Write CF 譬如对于 TiKV 的 Write CF 来说,我们的 key 的组织方式是 key + version 的方式,譬如这样:

key1 - ver 1 key1 - ver 2 ...... key1 - ver n key2 - ver 1 key2 - ver 2 ...... key2 - ver n ......

在 Write CF 的逻辑里面,我们只会用一个不带 version 的 key 来判断这个 key 是否存在,所以我们只能用 seek(key) 的方式,然后看得到的 key 去掉 version 之后是否就是我们需要的。seek 在数据量大的时候会存在明显的性能问题,因为它要在所有的 level 上面都要定位一下,但现在的机制我们又没法改成 get,虽然后面可能重构,但现在首要机制就是保证 seek 的性能。如果 seek 也能用 bloom filter,那么就会减少很多无用的文件定位了,所以我们在这里使用了 prefix_extractor 机制,将不带版本的 key 放到了 bloom filter 里面,提升 seek 的性能。
Raft CF 而对于 Raft CF 来说,key 的格式类似 region ID + log ID 的方式,譬如:
region 1 : log 1 ...... region 1 : log n region 2 : log 1 ...... region 2 : log n ......

即使系统里面有太多的 region,但同一个时刻同时活跃的 region 数量并不多,所以通常我们只会在一段时间处理一批 region 的 Raft log。也就是在 RocksDB 里面 memtable 的实际 region 数量是不会特别多的,但因为这些 region 很活跃,所以插入 Raft log 的性能压力很大。
同时我们也观察到,对于一个 region 来说,它的 log 插入是顺序的,也就是第一次插入 log 100,下一次一定是 log 101,也就是一定在 log 100 的后面,这个是 Raft 保证的,所以我们可以使用 memtable_insert_with_hint_prefix_extractor 机制,使用一个 hint 指针放在 log 100 那个地方,这样当下一次需要插入 log 101 的时候,直接从 hint 下一个位置插入,而不需要再在 memtable 里面进行多次比较了。
Prefix Extractor 上面两个优化都是使用了 prefix extractor ,Prefix extractor 是一个 SliceTransform object,定义如下:
// SliceTranform is a generic pluggable way of transforming one string // mainly used for prefix blooms. pub trait SliceTransform { // Extract a prefix from a specified key fn transform<'a>(&mut self, key: &'a [u8]) -> &'a [u8]; // Determine whether the specified key is compatible with the logic // specified in the Transform method. This method is invoked for every // key that is inserted into the db. If this method returns true, // then Transform is called to translate the key to its prefix and // that returned prefix is inserted into the bloom filter. If this // method returns false, then the call to Transform is skipped and // no prefix is inserted into the bloom filters. fn in_domain(&mut self, key: &[u8]) -> bool; // This is currently not used and remains here for backward compatibility. fn in_range(&mut self, _: &[u8]) -> bool { true } }

我们重点关注 transformin_domaintransform 就是提取 key 的 prefix,注意,虽然这里说明的是前缀 prefix,实际我们也可以提取后缀 postfix,或者中间某一段。in_domain 用来判断这个 key 是否符合提取的要求,如果返回了 true,则表明可以使用 transform 提取 prefix 并插入到 bloom filter 里面。
可以看到,无论是自定义 prefix extractor 还是使用,都是非常简单的,但需要注意几个地方:
  • 如果我们仍然要支持完整的 key Get 操作,那么需要设置 whole_key_filtering 为 true,这样不光 prefix 会加入到 bloom filter 里面,整个 key 也会加入进去。
  • 如果我们仍然需要 iterator 顺序 scan 所有的数据,需要将 total_order_seek 设置为 true,不然 scan 出来的数据顺序就不对。
小结 【Tuning|Tuning RocksDB - Prefix Extractor】Prefix extractor 只是一个非常简单的功能,仅仅是通过使用 prefix extractor,TiKV 在性能上面就有了将近 20% 以上的提升。后续我们也会更加深入的研究 RocksDB 其他的一些性能提升特性。

    推荐阅读