常见 KV Store
常见kv存储有
- 基于内存的 redis
- 基于单机磁盘的 leveldb、rocksdb
- 分布式(超过 TB 级别的) cassandra、hbase
RocksDB
- RocksDB
- RocksDB 中文网
- RocksDB 引擎: 5分钟全面了解其原理
- RocksDB 笔记
- Overview
- 深入理解什么是LSM-Tree
- Rocksdb 写流程,读流程,WAL文件,MANIFEST文件,ColumnFamily,Memtable,SST文件原理详解
安装
rocksdb 是 c++ 写的,由于官方没有提供对应平台的二进制库,不同平台编译的类库是不通用的,所以需要自己去相应的平台编译使用。如果使用 rocksdbjava 这个问题就好很多,但是 java api 总是落后于 c++ 版本的。
在安装 rocksdb 之前,需要理解一下什么是嵌入式的 kv-store。对于非嵌入式的 kv-store,比如 redis,想使用 redis 就需要先安装 redis-server,然后通过 redis-client 对数据进行读写。而对于嵌入式的 kv-store 来讲,是没有具体服务端的,直接用代码操作数据存储,也就是说只需要在你的项目中引入 rocksdb 的相关依赖(java 在 maven 中添加依赖),就可以在开发了。
C++
# 如果 ./configure 时报错 configure: error: C++ compiler cannot create executables,这种是缺少 c++ 组件
$ yum install -y gcc gcc-c++ gcc-g77
# Install gflags
$ git clone https://github.com/gflags/gflags.git
$ cd gflags
$ git checkout v2.0
$ ./configure && make && sudo make install
# Install snappy:
$ sudo yum install -y snappy snappy-devel
# Install zlib:
$ sudo yum install -y zlib zlib-devel
# Install bzip2:
$ sudo yum install -y bzip2 bzip2-devel
# Install lz4:
$ sudo yum install -y lz4-devel
# Install ASAN (optional for debugging):
$ sudo yum install -y libasan
# Install zstandard:
$ wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
$ mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
$ tar zxvf zstd-1.1.3.tar.gz
$ cd zstd-1.1.3
$ make && sudo make install
# Install rocksdb
$ git clone https://github.com/facebook/rocksdb.git
$ cd rocksdb
# 编译静态库,release mode,获得librocksdb.a
$ make static_lib
# 编译动态库,release mode,获得librocksdb.so
$ make shared_lib
$ cp librocksdb.a /usr/lib
$ cp -r include/ /usr/include
$ cp librocksdb.so /usr/lib
$ sudo ldconfig
Java
<dependency>
<groupId>org.rocksdb</groupId>
<artifactId>rocksdbjni</artifactId>
<version>5.17.2</version>
</dependency>
管理工具
使用ldb
和sst_dump
来管理 rocksdb,在安装了上述 c++ 依赖之后
# Linux
$ cd rocksdb
$ make ldb sst_dump
$ cp ldb /usr/local/bin/
$ cp sst_dump /usr/local/bin/
# OSX
$ brew install rocksdb
# 对应的命令分别为
$ rocksdb_ldb
$ rocksdb_sst_dump
关于ldb
和sst_dump
使用方法,可以参考官方文档https://github.com/facebook/rocksdb/wiki/Administration-and-Data-Access-Tool
examples
查看所有数据库的所有 keys
#!/bin/bash
export DATA_PATH=/hadoop/rockfs/data
export DB_INSTANCES=$(ls /hadoop/rockfs/data)
for i in $DB_INSTANCES; do
ldb --db=$DATA_PATH/$i/ scan 2>/dev/null;
done
查看 SST file properties
# 注意 --file 需要指定绝对路径
$ sst_dump --file=$DATA_PATH/test --show_properties
from [] to []
Process /hadoop/rockfs/data/0006.sst
Sst file format: block-based
Table Properties:
------------------------------
# data blocks: 26541
# entries: 2283572
raw key size: 264639191
raw average key size: 115.888262
raw value size: 26378342
raw average value size: 11.551351
data block size: 67110160
index block size: 3620969
filter block size: 0
(estimated) table size: 70731129
filter policy name: N/A
# deleted keys: 571272
# 如果出现 sst_dump: error while loading shared libraries: libgflags.so.2: cannot open shared object file: No such file or directory
$ find / -name libgflags.so.2
/root/gflags/.libs/libgflags.so.2
/usr/local/lib/libgflags.so.2
# 可以看出依赖已经下载到了,只是没有被找到,手动指定下依赖库
$ vim /etc/ld.so.conf.d/usr-libs.conf
/usr/local/lib
$ sudo ldconfig
升级 gcc 到 8.1
http://mirror.hust.edu.cn/gnu
下载gmp-5.1.1.tar.bz2
、mpfr-3.1.5.tar.bz2
、mpc-1.1.0.tar.gz
、gcc-8.0.1.tar.gz
$ yum install -y m4
$ tar -xvf gmp-5.1.1.tar.bz2
$ cd gmp-5.1.1
$ ./configure --prefix=/usr/local/gmp-5.1.1 && make
$ make install
$ tar -xvf mpfr-3.1.5.tar.bz2
$ cd mpfr-3.1.5
$ ./configure --prefix=/usr/local/mpfr-3.1.5 --with-gmp=/usr/local/gmp-5.1.1 && make
$ make install
$ tar -zxvf mpc-1.1.0.tar.gz
$ cd mpc-1.1.0
$ ./configure --prefix=/usr/local/mpc-1.1.0 --with-gmp=/usr/local/gmp-5.1.1 --with-mpfr=/usr/local/mpfr-3.1.5 && make
$ make install
$ tar -zxvf gcc-8.0.1.tar.gz
$ export LD_LIBRARY_PATH=/usr/local/mpc-1.1.0/lib:/usr/local/gmp-5.1.1/lib:/usr/local/mpfr-3.1.5/lib:$LD_LIBRARY_PATH
$ cd gcc-8.0.1
$ ./configure --prefix=/usr/local/gcc-8.0.1 --enable-threads=posix --disable-checking --disable-multilib --enable-languages=c,c++ --with-gmp=/usr/local/gmp-5.1.1 --with-mpfr=/usr/local/mpfr-3.1.5 --with-mpc=/usr/local/mpc-1.1.0 && make
$ make install
LSM-Tree(Log-Structured-Merge Tree)
背景
LSM 通过消去随机的磁盘IO来提供比传统的 B+ 树或者 ISAM 更好的写操作吞吐量。本质问题是,不论HDD
还是SSD
,随机读写都比顺序读写要慢,尤其是机械磁盘,但是,如果我们是顺序写磁盘的话,那速度跟写内存是相当的:因为减少了寻道时间和旋转时间。而且顺序写的情况下,还能将数据先放到buffer
,等待数量达到磁盘的一页时,再落盘,能进一步减少磁盘IO次数。
比如日志数据,他们是有严格的时间顺序的,因此完全可以顺序写入。当你想要查找数据的时候需要扫描所有的数据,类似grep
,因此查找的速度就比较慢O(n)
。
对于数据量比较多的情况,提高读取性能的一般方式
- 二分查找: 将文件数据按
key
排序保存,使用二分查找来完成特定key
的查找,O(logN)
。 - 哈希: 用哈希将数据分割为不同的
bucket
,当查询某个数据的时候通过哈希函数获取数据所在的bucket
,然后遍历bucket
中少量的数据。 - B+树: 使用
B+
树或者ISAM
等方法,可以减少外部文件的读取 - 外部文件: 将数据保存为日志,并创建一个
hash
或者查找树映射相应的文件。
上面的方法通过按照固定的数据结构编排、存储数据,有效的提高了读取的性能,但是也因此导致增加了大量的随机IO,丧失了良好的写入性能,尤其在数据量很大的时候。比如更新Hash
和B+ Tree
的结构的时候,还需要移动其中的部分数据,导致随机IO。
那么当数据不是日志这种严格有序的数据,如何保证数据按照某个顺序排好序然后顺序写入磁盘,还能支持数据的快速检索?听起来貌似很矛盾,但LSM
就是做这件事情的。
LSM 原理
LSM
使用一种不同于上述四种的方法,保持了日志文件写性能,以及微小的读操作性能损失。本质上就是让所有的操作顺序化,避免对磁盘进行随机读写。如何做到呢?
LSM
的思想,在内存中进行数据的增加和修改,达到指定的限制后再将这些修改操作批量写入到磁盘中,相比较于写入操作的高性能,读取需要查询两个地方的数据,内存中最近修改的操作和磁盘中历史的数据,先看是否在内存中,若没有命中,还要访问磁盘文件。
LSM
的基本原理,将之前使用一个大的查找结构(造成随机读写,影响写性能),变换为将写操作顺序的保存到一些相似的有序文件sstable
中,每个文件包含短时间内的一些改动。因为文件是有序的,所以之后查找也会很快。文件是不可修改的,他们永远不会被更新,新的更新操作只会写到新的文件中。读操作检查很有限的文件。而后台则通过周期性的合并这些文件来减少文件个数。
写入原理
当有新的更新操作,首先把他们写到内存缓存中,也就是memtable
中,memtable
使用树结构来保持key
有序,为了保证数据不丢失,大部分实现,都会在操作写入内存之前先写WAL
到磁盘。当memtable
数据达到一定规模的时候会被flush
到磁盘生成一个sstfile
,在flush
的过程,系统只进行了顺序磁盘读写,文件是不可修改的,对于数据的修改,则是通过写入新的文件覆盖旧文件中的数据。
所以当越多的数据被写入系统,就会产生越来越多的不可修改,有序的sstfile
被顺序写入磁盘,每一个sstfile
都代表了小的,按时间顺序的修改操作,也就是说新生成的sstfile
中的修改会覆盖掉旧的sstfile
。
因为文件不可以被修改,旧文件中的有些数据可能已经无效了,因此系统需要周期性的执行合并操作(Compaction)。合并操作会选择一些文件,把它们合并到一起,合并的过程会把那些没有用的记录合并或者删除,除了丢与冗余和无效数据,合并之后会也大大减少sstfile
的数量,这样就能够长期保证读取性能。因为每个sstfile
内部都是有序的,因此sstfile
的合并也是非常高效的。
读取原理
当来一个读取操作,系统会首先检查memtables
,如果没有找到这个key
,就会逆序(新的sst
数据优先与旧的sst
)一个个的检查sstfile
,直到key
被找到或者遍历完所有的sstfile
。对于单个sstfile
的查找则是O(logN)
,但当如果sstfile
数量很多的情况下,最坏情况下可能每个sstfile
都要被检查,因此整体的时间复杂度为O(k*logN)
,k
为sstfile
的数量。
因此LSM
的读操作比其他本地更新的结构慢,有一些技巧可以提高性能。
- 最基本的方法就是页缓存,如
LevelDB
中的TableCache
,将sstable
按照LRU
缓存缓存在内存中,减少二分查找的消耗。 - 为每个文件创建索引,使用
Block Index
- 即便每个文件有索引,随着文件数量的增加,读操作依然会很慢。而且每次读操作还是要访问大量的文件,有没有一种方式能够快速的告诉我们我们想要的数据是不是在当前
sstfile
中,还不需要访问大量索引文件呢?答案是使用Bloom Filter
,布隆过滤器如果告诉你key
不在当前集合中,就百分之一百不会存在,但他告诉你在的话,却不一定在,有一定的概率实际上你想要查找的key
并不在当前集合中,但这不影响Bloom Filter
的高效,它避免的没有必要的文件读取。
所有的写操作都被分批处理,只写到顺序块上。另外,合并操作的周期操作会对IO有影响,读操作有可能会访问大量的文件(散乱的读)。这简化了算法工作的方法,我们交换了读和写的随机IO。这种折衷很有意义,我们可以通过软件实现的技巧像布隆过滤器或者硬件(大文件cache)来优化读性能。
Basic Compaction
总结
LSM
是日志和传统的单文件索引(B+ tree
,Hash Index
)的中立,他提供一个机制来管理更小的独立的索引文件(sstable
)。通过管理一组索引文件而不是单一的索引文件,LSM
将B+
树等结构昂贵的随机IO变的更快,而代价就是读操作要处理大量的索引文件(sstable
)而不是一个,另外还是一些IO被合并操作消耗。
参考
Terminology
存储类型
RocksDB 有三个非常重要的结构memtable
、sstfile
和logfile
。
memtable
: 顾名思义是一个内存中的数据结构,memtable
有分为两种,一种叫做active memtable
,另一种叫做immutable memtable
,当active memtable
被写满就会变成immutable memtable
,刷入到sstfile
,成功保存到磁盘之后就可以将内存中的数据清空掉了。The format of a default sstfile。sstfile
:sorted string table
,有序字符串表,这个有序字符串就是数据的key
,sst
是分层的,一层比一层大。上层的数据和下层数据的版本新于下层数据,当上层key
命中后,不会查询下层的key
,并且只有在compaction
的时候才会删除旧版本的key
logfile
: 当新的数据被写入时会被插入到memtable
,通过选择开启WAL
可以在写入之前先将数据操作写入logfile
来保证内存写入数据的不丢失。logfile
是一个磁盘上的顺序写入的日志文件。
Compaction
compaction
主要包括两类:将内存中imutable
dump 到磁盘上sst
的过程称之为flush
或者minor compaction
。磁盘上的sst
文件从低层向高层转储的过程称之为compaction
或者是major compaction
。对于minor compaction
和major compaction
分别对应一组线程,通过参数rocksdb_max_background_flushes和rocksdb_max_background_compactions可以来控制。通过minor compaction,内存中的数据不断地写入的磁盘,保证有足够的内存来应对新的写入;而通过major compaction,多层之间的SST文件的重复数据和无用的数据可以迅速减少,进而减少sst文件占用的磁盘空间。对于读而言,由于需要访问的sst文件变少了,也会有性能的提升。由于compaction过程在后台不断地做,单位时间内compaction的内容不多,不会影响整体的性能,当然这个可以根据实际的场景对参数进行调整,compaction的整体架构可以参见图1。了解了compaction的基本概念,下面会详细介绍compaction的流程,主要包括两部分flush(minor compaction),compaction(major compaction),对应的入口函数分别是BackgroundFlush和BackgroundCompaction。
RocksDB Tuning
Amplification factors (放大因素)
其实对数据库系统的调优很大一部分工作是在写放大、读放大和空间放大这三个放大因子之间做取舍。
- 写放大: 平均写入
1
个字节,引擎中在数据的声明周期内实际会写入n
个字节(比如compaction
操作会将数据合并压缩后再写一遍),其写放大率是n
。也就是如果在业务方写入速度是10mb/s
,在引擎端或者操作系统层面能观察到的数据写入速度是30mb/s
,这时,系统的写放大率就是3
。写放大过大会制约系统的实际吞吐。并且对于SSD来说,越大的写放大,也会导致SSD寿命缩短。 - 读放大: 一个小的读请求,系统所需要读去
n
个页面来完成查询,其读放大率是n
。逻辑上的读操作可能会命中引擎内部的cache
或者文件系统cache
,命中不了cache
就会进行实际的磁盘IO,命中cache
的读取操作的代价虽然很低,但是也会消耗 cpu。 - 空间放大: 就是平均存储
1
个字节的数据,在存储引擎内部所占用的磁盘空间n
个字节,其空间放大是n
。比如写入10mb
的数据,大小在磁盘上实际占用了100mb
,这是空间放大率就是10
。空间放大和写放大在调优的时候往往是互斥的,空间放大越大,那么数据就不需要频繁的compaction
,其写放大就会降低;如果空间放大率设置的小,那么数据就需要频繁的compaction
来释放存储空间,导致写放大增大。
Statistics
statistics
是 RocksDB 用来统计系统性能和吞吐信息的功能,开启它可以更直接的提供性能观测数据,能快速发现系统的瓶颈或系统运行状态,由于统计信息在引擎内的各类操作都会设置很多的埋点,用来更新统计信息,但是开启statistics
会增加5%~10%
的额外开销。
** Compaction Stats **
Level Files Size(MB) Score Read(GB) Rn(GB) Rnp1(GB) Write(GB) Wnew(GB) Moved(GB) W-Amp Rd(MB/s) Wr(MB/s) Comp(sec) Comp(cnt) Avg(sec) Stall(sec) Stall(cnt) Avg(ms) KeyIn KeyDrop
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
L0 2/0 15 0.5 0.0 0.0 0.0 32.8 32.8 0.0 0.0 0.0 23.0 1457 4346 0.335 0.00 0 0.00 0 0
L1 22/0 125 1.0 163.7 32.8 130.9 165.5 34.6 0.0 5.1 25.6 25.9 6549 1086 6.031 0.00 0 0.00 1287667342 0
L2 227/0 1276 1.0 262.7 34.4 228.4 262.7 34.3 0.1 7.6 26.0 26.0 10344 4137 2.500 0.00 0 0.00 1023585700 0
L3 1634/0 12794 1.0 259.7 31.7 228.1 254.1 26.1 1.5 8.0 20.8 20.4 12787 3758 3.403 0.00 0 0.00 1128138363 0
L4 1819/0 15132 0.1 3.9 2.0 2.0 3.6 1.6 13.1 1.8 20.1 18.4 201 206 0.974 0.00 0 0.00 91486994 0
Sum 3704/0 29342 0.0 690.1 100.8 589.3 718.7 129.4 14.8 21.9 22.5 23.5 31338 13533 2.316 0.00 0 0.00 3530878399 0
Int 0/0 0 0.0 2.1 0.3 1.8 2.2 0.4 0.0 24.3 24.0 24.9 91 42 2.164 0.00 0 0.00 11718977 0
Flush(GB): accumulative 32.786, interval 0.091
Stalls(secs): 0.000 level0_slowdown, 0.000 level0_numfiles, 0.000 memtable_compaction, 0.000 leveln_slowdown_soft, 0.000 leveln_slowdown_hard
Stalls(count): 0 level0_slowdown, 0 level0_numfiles, 0 memtable_compaction, 0 leveln_slowdown_soft, 0 leveln_slowdown_hard
** DB Stats **
Uptime(secs): 128748.3 total, 300.1 interval
Cumulative writes: 1288457363 writes, 14173030838 keys, 357293118 batches, 3.6 writes per batch, 3055.92 GB user ingest, stall micros: 7067721262
Cumulative WAL: 1251702527 writes, 357293117 syncs, 3.50 writes per sync, 3055.92 GB written
Interval writes: 3621943 writes, 39841373 keys, 1013611 batches, 3.6 writes per batch, 8797.4 MB user ingest, stall micros: 112418835
Interval WAL: 3511027 writes, 1013611 syncs, 3.46 writes per sync, 8.59 MB written
Parallelism options
LSM 架构的引擎,后台主要有两种线程,flush
和compaction
。在RocksDB中,两种线程有不同的优先级,flush
优先级高,compaction
优先级低。为了充分利用多核,RocksDB中的两种线程可以配置线程数。
max_background_flushes
: 是后台memtable
dump 成sstable
的并发线程数。默认是1
,但是当使用多个column family
时,内部会存在多个memtable
,可能会同时发生flush
,如果线程是1
,在写入量大的情况下,可能会导致flush
不及时,出现无法写入的情况。max_background_compactions
: 是后台sstable flush
的线程数量,因为一般 RocksDB 会有多个level
,涉及多个sstable
文件,并发compaction
会加快compaction
的速度,但是如果compaction
过慢,达到soft_pending_compaction_bytes_limit
会发生阻塞,达到hard_pending_compaction_bytes
会停写。
General options
filter_policy
: 这个就是每个sstable
的bloom filter
,使用bloom filter
可以大幅减少不必要的磁盘IO。在bits_per_key
为10
的情况下,bloom filter
错误率估计为1%
,也就是存在如下情况:有1%
的概率出现错误,key 实际上不存在于集合中,但是在bloom filter
查询的结果错误的认为是存在的。这种情况导致会有1%
的不必要的磁盘IO。bloom filter
认为 key 不存在在集合中,就一定不存在。当然bits_per_key
越大,bloom filter
误判率越低,但是占用的内存和写放大会相应增加。block_cache
: 可以配置大小的LRU cache
,用来缓存未经过压缩的block
。由于访问cache
需要加锁访问,当大部分数据都在cache
中时,多线程并发访问cache
可能会出现锁竞争的瓶颈,所以LRU cache
还有一个shard_bits
参数,将LRU cache
分片,其实就是对锁进行拆分,不同分片的cache
不会出现竞争。默认shard_bits
是6,那么cache
的shard
数目就是2^6=64
。allow_os_buffer
: 操作系统buffer
是用来缓存sstable
文件的cache
,sstable
在文件中是压缩的,所以操作系统buffer
是对磁盘上的sstable
文件block
的cache
。max_open_files
: 最大打开文件数。RocksDB 对于打开文件的句柄也会放在cache
中,当sstable
文件过多,超过max_open_files
限制后,会将cache
中淘汰的文件句柄强制关闭,下次读取文件的时候再次打开。table_cache_numshardbits
: 和block_cache
中的shard_bits
作用一致,主要是为了拆分互斥锁。block_size
: RocksDB 中sstable
文件的由block
组成,block
也是文件读写的最小逻辑单位,当读取一个很小的key
,其实会读取一个block
到内存,然后查找数据。默认的block_size
大小为4KB
。每个sstable
文件都会包含索引的block
,用来加快查找。所以block_size
越大,index
就会越少,也会相应的节省内存和存储空间,降低空间放大率,但是会加剧读放大,因为读取一个key
实际需要读取的文件大小随之增加了。
Sharing cache and thread pool
有时候我们想在一个进程中运行多个 RocksDB 实例,RocksDB 为这些实例提供了一种共享block cache
和thread pool
的方法。要共享block cache
,需要为所有实例分配单个cache object
first_instance_options.block_cache = second_instance_options.block_cache = rocksdb::NewLRUCache(1GB)
这样两个实例就会共享总大小为1GB
的block cache
Flushing options
RocksDB 中的写入在写WAL
后会先写入memtable
,memtable
达到特定大小后会转换成immutable membtale
,然后会将数据从内存flush
到磁盘的sstable
。
write_buffer_size
: 配置单个memtable
的大小,当memtable
达到指定大小,会自动转换成immutable memtable
并且新创建一个memtable
。max_write_buffer_number
: 指定一个 RocksDB 中memtable
和immutable memtable
总的数量。当写入速度过快,或者flush
线程速度较慢,出现memtable
数量超过了指定大小,请求会无法写入。min_write_buffer_number_to_merge
:immutable memtable
在flush
之前先进行合并,比如参数设置为2
,当一个memtable
转换成immutable memtable
后,RocksDB 不会进行flush
操作,等到至少有2
个后才进行flush
操作。这个参数调大能够减少磁盘写的次数,因为多个memtable
中可能有重复的key
,在flush
之前先merge
后就避免了旧数据刷盘;但是带来的问题是每次数据查找,当memtable
中没有对应数据,RocksDB 可能需要遍历所有的immutable memtable
,会影响读取性能。
例如:
write_buffer_size = 512MB
max_write_buffer_number = 5
min_write_buffer_number_to_merge = 2
按上面的配置,如果数据写入速度是16MB/s
,每32秒会生成一个新的memtable
,每64秒会生成两个memtable
进行merge
和flush
操作。如果flush
速度很快,memtable
占用的内存应该小于1.5G
,当磁盘IO繁忙,flush
速度慢,最多会有5个memtable
,占用内存达到2.5G
,后续就无法写入。
Level Style Compaction
Write stalls
RocksDB 在flush
或compaction
速度来不及处理新的写入,会启动自我保护机制,延迟写或者禁写。主要有几种情况:
Too many memtables
- 写限速: 如果
max_write_buffer_number
大于3
,将要flush
的memtables
大于等于max_write_buffer_number - 1
,write
会被限速。 - 禁写:
memtable
个数大于等于max_write_buffer_number
,触发禁写,等到flush
完成后允许写入
Stalling writes because we have 4 immutable memtables (waiting for flush), max_write_buffer_number is set to 5
Stopping writes because we have 5 immutable memtables (waiting for flush), max_write_buffer_number is set to 5
Too many level-0 SST files
- 写限速:
L0
文件数量达到level0_slowdown_writes_trigger
,触发写限速。 - 禁写:
L0
文件数量达到level0_stop_writes_trigger
,禁写。
Stalling writes because we have 4 level-0 files
Stopping writes because we have 20 level-0 files
Too many pending compaction bytes
- 写限速: 等待
compaction
的数据量达到soft_pending_compaction_bytes
,触发写限速。 - 禁写: 等待
compaction
的数据量达到hard_pending_compaction_bytes
,触发禁写。Stalling writes because of estimated pending compaction bytes 500000000 Stopping writes because of estimated pending compaction bytes 1000000000
当出现write stall
时,可以按具体的系统的状态调整如下参数:
- 调大
max_background_flushes
- 调大
max_write_buffer_number
- 调大
max_background_compactions
- 调大
write_buffer_size
- 调大
min_write_buffer_number_to_merge
Bloom filters and index
RocksDB 中使用bloom filter
来尽可能避免不必要的sstable
访问,在 RocksDB 中bloom filter
有两种: block-based filter
和full filter
。
block-based filter
这种filter
是保存在每个block
内部,一个sstable file
有多个block
。所以每次访问sstable
需要先访问一次block
索引,找到对应的block
后加载bloom filter
或者是在cache
中获取对应的filter
查询。
full filter
每个sstable
文件只有一个filter
,在访问sstable
之前可以事先判断key
的存在性,能够避免不存在key
的索引访问。
一个典型的256MB
的SST
文件的index/filter
的大小在0.5~5MB
,比系统内默认的block
大很多倍,这种场景对于block cache
不友好,LRU cache
是按block
来进行置换,一部分block
失效会导致重复读取多次SST
加载index
和filter
。
对于上面大SST
文件的index
或者filter block
,RocksDB 支持两级index
,切分index/filter
。每次读取cache
的粒度变小,cache
更高效。
bloom_bits_per_key
: 平均每个 key 需要的bloom filter
空间。配置bloom filter
的容量,默认是10
,存在1%
的判错概率。cache_index_and_filter_blocks_with_high_priority
: 可以配置 RocksDB 高优先 cache index和filter,优先剔除数据block
。pin_l0_filter_and_index_blocks_in_cache
: 使level 0
中的SST
的index
和filter
常驻cache
。
配置实例
存储介质 flash
thread_pool = 4
options.options.compaction_style = kCompactionStyleLevel;
options.write_buffer_size = 67108864; // 64MB
options.max_write_buffer_number = 3;
options.target_file_size_base = 67108864; // 64MB
options.max_background_compactions = 4;
options.level0_file_num_compaction_trigger = 8;
options.level0_slowdown_writes_trigger = 17;
options.level0_stop_writes_trigger = 24;
options.num_levels = 4;
options.max_bytes_for_level_base = 536870912; // 512MB
options.max_bytes_for_level_multiplier = 8;
全内存
options.allow_mmap_reads = true;
BlockBasedTableOptions table_options;
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
table_options.no_block_cache = true;
table_options.block_restart_interval = 4;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.level0_file_num_compaction_trigger = 1;
options.max_background_flushes = 8;
options.max_background_compactions = 8;
options.max_subcompactions = 4;
options.max_open_files = -1;
ReadOptions.verify_checksums = false
Snapshot
A snapshot captures a point-in-time view of the DB at the time it’s created. Snapshots do not persist across DB restarts.
Data Files
先看一下 RocksDB 存储在磁盘上的文件有哪些
$ ls -l /$ROCKSDB_DATA_DIR
-rw-r--r-- 1 root root 13K Sep 6 07:17 057373.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057374.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057375.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057376.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057377.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057378.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057379.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057380.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057381.sst
-rw-r--r-- 1 root root 13K Sep 6 07:17 057382.sst
-rw-r--r-- 1 root root 16 Sep 5 21:11 CURRENT
-rw-r--r-- 1 root root 37 Sep 5 21:11 IDENTITY
-rw-r--r-- 1 root root 0 Sep 5 21:11 LOCK
-rw-r--r-- 1 root root 267M Sep 17 09:01 LOG
-rw-r--r-- 1 root root 3.3M Sep 6 07:17 MANIFEST-000006
-rw-r--r-- 1 root root 31K Sep 5 21:11 OPTIONS-000024
-rw-r--r-- 1 root root 33K Sep 5 21:11 OPTIONS-000026