leveldb切rocksdb中遇到的那些坑
1 LevelDB与RocksDB简介
LevelDB与RocksDB都是一种持久化存储的KV系统,RocksDB是基于LevelDB进行开发的,二者都是基于LSM-tree(Log Structured Merge Trees)的结构进行存储。如下图所示,LevelDB与Rocksdb写数据时,都是通过先将数据记录到WAL中,用于发生故障时进行恢复,再将数据写入内存中的Memtable,等到Memtable中数据写满之后,Memtable会转变成Immutable Memtable,Immutable Memtable会dump成底层存储文件(即SSTable,在LevelDB以ldb为后缀结尾,在RocksDB中以sst为后缀结尾)。读取数据时,会优先读取内存中Memtable中的内容,没有再依次去查找Immutable Memtable,和底层存储文件(包含一些存储结构cache中的查找操作),由于底层存储文件是分层存储的,并且每层存在多个SSTable,会依次查找每层的每个SSTable,因此在LevelDB与RocksDB中,写性能都优于读性能。
2 RocksDB的优势
2.1 Memtable
Memtable是在内存中存储k-v记录的表,LevelDB在内存中只有一个Memtable,来不及刷盘便会造成写入时系统卡顿,而RocksDB可以设置多个Memtable,当一个Memtable写满了后,立即创建一个新的Memtable,后续写入会写到新的Memtable中。Memtable的数量可以进行调优。
2.2 Compaction
为了加速LevelDB和RocksDB的读性能,二者都在底层进行了Compaction操作。在LevelDB中主要包含Minor Compaction(用于将Immutable Memtable dump到磁盘中的存储文件中,对应于RocksDB中的Flush)和Major Compaction(用于整理底层存储文件,清理一些无效的记录和数据)两种Compaction操作。RocksDB中的Compaction主要指Major Compaction,同时RocksDB提供了很多中Compaction策略,默认为Level Compaction。Compaction主要通过挑选level n-1层的sst与level n层的sst文件,将level n-1层的文件与level n层的sst文件进行合并,将数据一层一层下沉。当然,频繁的Compaction可能也会带来一些性能问题,造成cache的失效。
LevelDB后台是单线程的,只能单线程合并文件,各种Compaction任务会加到任务队列依次执行,而RocksDB提供了多个线程,将Flush线程和Compaction线程分开, 在刷盘的同时可以进行多线程合并底层存储文件。RocksDB还为线程池提供了优先级管理,有着优秀的线程调度。频繁的Compaction操作伴随着SST的写入,会带来较大的IO压力,因此可以针对项目进行Compaction速率的调优。同时,RocksdDB中的后台线程也是可以停止的,可以通过调用PauseBackgroundWork()和ContinueBackgroundWork()灵活控制后台线程的停止和启动,但是需要注意的是这里会停止所有的后台线程(包含flush线程与compaction线程),停止flush线程后,继续执行写操作,在内存中的Memtable写满后,会卡住,调用ContinueBackgroundWork()之后,恢复正常。由于底层存储文件第0层是无序的,不同的SST存在重叠的情况,所以不能并行,RocksDB提供了subCompaction,将第0层并行合并到第1层,默认没有开启,后续可以针对此处进行优化。
2.3 其他优化
RocksDB支持一次获取多个k-v, 并且支持在合并的时候使用过滤器对无效记录进行过滤,以及一些解析底层存储文件sst的工具供开发者使用,同时你可以直接创建底层存储文件sst, 通过SstFileWriter直接将创建的sst导入到RocksDB。
3 LevelDB迁移到RocksDB
LevelDB与RocksDB大部分接口都是兼容的,磁盘上的目录结构也是相似的,迁移相对容易。在创建数据库时,如果需要保持和以前的配置大致相同,可以使用rocksdb::LevelDBOptions将之前的leveldb::Options对象转化为rocksdb::Options,options里包含了数据库的一些配置选项。需要注意的是,RocksDB中存在了列簇(column family)概念,会对数据库进行逻辑上的划分,可以将不相关的数据结构存储在同一个DB中,因为LevelDB没有此概念,迁移到RocksDB之后,默认不设置,会将数据都存储到default列簇中。
如果设置多个列簇,那么它们分别有自己的Memtable和Immutable Memtable,以及相同的存储文件,但是会共同写入同一个WAL中。对于Memtable的数量和后台线程的个数可以根据自己的项目进行设置。
在迁移过程中遇到一个问题,我们的项目中,创建了较多数量的LevelDB实例,当我们迁移到RocksDB之后,发现程序启动后,存储空间会上涨几个G的大小,通过ls查看RocksDB目录文件,并没有发现异常,但通过du命令查看会发现,RocksDB的日志文件在没有存储任何东西的情况下直接就会占到71M的大小(ls查看的是文件实际大小,而du查看的是文件实际占用磁盘的空间大小),通过查阅资料发现,LevelDB中默认配置Memtable大小为4kb,而RocksDB内存中的Memtable大小为默认为64M,为了防止磁盘没有空间写,提前为RocksDB的日志在磁盘上进行了预分配,如果不希望占用这么多空间,可以通过RocksDB::Options中的write_buffer_size调整Memtable的大小,进而影响日志文件在磁盘中预分配的大小。
- 点赞
- 收藏
- 关注作者
评论(0)