RocksDB是Facebook基于LevelDB开发的开源持久化KV存储引擎。Rocksdb针对写密集型场景,架构采用LSM Tree(log structured merge tree),将随机写转为顺序写来提升写性能,数据有序存储,缺点是读性能的损失和写放大的增加。
MySQL, TiDB, Ceph, MongoDB, Flink等数据库的存储引擎都有采用Rocksdb。MySQL默认存储引擎是B+ tree 结构的InnorDB,也可以把存储引擎切换成Rocksdb称为MyRocks。
https://github.com/facebook/rocksdb
先上个图:
write:
1. Write首先顺序追加写硬盘上的WAL(Write-Ahead-Log)日志文件。先写WAL是为了防止突然掉电或系统崩溃等故障造成的内存数据丢失,WAL 日志可以完整恢复 Memtable 中的数据。当然也可以选择关闭WAL功能,但不推荐这么做。
DBOptions::max_total_wal_size # WAL最大size
DBOptions::WAL_ttl_seconds # WAL删除时间
2. 然后再并发写入内存中的Memtable。为了保证数据的有序性和高效的插入,Memtable基于跳表(skipList)实现插入有序。
下图链表的指针我 就不画了,实线是查找位置真正走的路径,虚线只是对比后在两者范围内则跳到下一级。这样一层层的找到最终地点。
3. 当active Memtable达到设置的触发size时(默认64MB),转为只读状态的immutable,并创建新的memtable继续写入。memtable的大小可以设置,write_buffer_size,# memtable最大size
4. immutable中相同的key进行合并,接着flush到磁盘上的SST(Sorted String Table,默认64MB)文件中。然后释放内存和WAL log。写入level0是顺序追加写入。
5.LSM tree有多个层级,每层有多个SST文件,内存中的数据只能flush到level0层。
level0:数据进入硬盘的第一层,从内存顺序追加写入,因此每个SST内数据有序无重叠,SST间无序切可以存在key重叠。可以配置这一层文件个数level0_file_num_compaction_trigger,默认为4。
level1-N: 从level0 merge数据合并,并和level1层进行compaction。level0之下的level容量是配置相比上层的指数增长(默认10倍),比如conf文件中level0是64MB, 倍数是10,那么level-levelN 按照10倍递增。无论是SST内部还是SST间key整体有序的,无重叠。
6.Compation: 继承与LevelDB,Level0达到文件个数时,所有文件与Level1层compation进入Level1,Level1及之后按照容量触发,当本层数据达到容量设置,本层的SST文件会和下一层的重叠或过期key进行合并去重,这样逐步的将数据从level0 推到levelN。
level0 compation 不能并行,level1及之后可以并行执行。有多个level超多阈值,可以根据score值(文件大小或个数除以阈值) 依次并行compation。
Write Stall:
1. memtable个数达到max_write_buffer_number,会完全停止等待所有flush完成
2. L0文件个数达到level0_slowdown_writes_trigger降速/ level0_stop_writes_trigger完全停止
3.pending_compaction_bytes(等待compaction的文件总大小)达到soft_pending_compaction_bytes/ hard_pending_compaction_bytes
调优:
1.如果内存充足memtable个数多状态,调整max_write_buffer_number* write_buffer_size
2.内存不足,max_background_flushes增加flush线程数,并带来IO消耗。
3.level0瓶颈,调整write_buffer_size即level0文件大小 和 level0_slowdown_writes_trigger
4.levelN瓶颈,调整max_background_compactions 合并线程数。
5.rate_limit_bytes_per_sec:控制每秒的compaction和flush的写入量。
6.refill_period_us:控制tokens多久被再次充满。比如,rate_bytes_per_sec是10M/s,同时refill_period_us是100ms,那么每100ms 的流量就是1MB。
7.fairness:用来均衡compaction和flush任务的参数。避免因为flush任务过多,导致compaction被饿死。
为了保证服务的稳定,需要不断尝试修改compaction和flush执行时期的写入速度。从而让IO看起来相对平滑一些,避免瞬时IO超时的问题。