嵌入式K/V数据处理库-RocksDB
2013-12-10 14:13:50

RocksDB 是一个来自 Facebook 的可嵌入的支持持久化的 key-value 存储系统,也可作为 C/S 模式下的存储数据库。


Persistent key-value store for fast storage environments
 
RocksDB 基于 LevelDB 构建,采用c/c++开发并在BSD协议下授权,关于 RocksDB 的性能说明请点这里

RocksDB is an embeddable persistent key-value store for fast storage. RocksDB can also be the foundation for a client-server database but our current focus is on embedded workloads.

RocksDB builds on LevelDB to be scalable to run on servers with many CPU cores, to efficiently use fast storage, to support IO-bound, in-memory and write-once workloads, and to be flexible to allow for innovation.

RocksDB是在LevelDB原来的代码上进行改进完善的,所以在用法上与LevelDB非常的相似。从继承的角度看,Rocksdb就像是Leveldb的晚辈。

RocksDB虽然在代码层面上是在LevelDB原有的代码上进行开发的,但却借鉴了Apache HBase的一些好的思路。在云计算横行的年代,开口不离Hadoop,RocksDB也开始支持HDFS,允许从HDFS读取数据。而LevelDB则是一个比较单一的存储引擎,有点我就是我,除了我依然只有我的感觉。也是因为LevelDB的单一性,在做具体的应用的时候一般需要对其作进一步扩展。

RocksDB支持一次获取多个K-V,还支持Key范围查找。LevelDB只能获取单个Key。

RocksDB除了简单的Put、Delete操作,还提供了一个Merge操作,说是为了对多个Put操作进行合并。站在引擎实现者的角度来看,相比其带来的价值,其实现的成本要昂贵很多。个人觉得有时过于追求完美不见得是好事,据笔者所测(包括测试自己编写的引擎),性能的瓶颈其实主要在合并上,多一次少一次Put对性能的影响并无大碍。

RocksDB提供一些方便的工具,这些工具包含解析sst文件中的K-V记录、解析MANIFEST文件的内容等。有了这些工具,就不用再像使用LevelDB那样,只能在程序中才能知道sst文件K-V的具体信息了。

RocksDB支持多线程合并,而LevelDB是单线程合并的。LSM型的数据结构,最大的性能问题就出现在其合并的时间损耗上,在多CPU的环境下,多线程合并那是LevelDB所无法比拟的。不过据其官网上的介绍,似乎多线程合并还只是针对那些与下一层没有Key重叠的文件,只是简单的rename而已,至于在真正数据上的合并方面是否也有用到多线程,就只能看代码了。

RocksDB增加了合并时过滤器,对一些不再符合条件的K-V进行丢弃,如根据K-V的有效期进行过滤。

压缩方面RocksDB可采用多种压缩算法,除了LevelDB用的snappy,还有zlib、bzip2。LevelDB里面按数据的压缩率(压缩后低于75%)判断是否对数据进行压缩存储,而RocksDB典型的做法是Level 0-2不压缩,最后一层使用zlib,而其它各层采用snappy。

在故障方面,RocksDB支持增量备份和全量备份,允许将已删除的数据备份到指定的目录,供后续恢复。

RocksDB支持在单个进程中启用多个实例,而LevelDB只允许单个实例。

RocksDB支持管道式的Memtable,也就说允许根据需要开辟多个Memtable,以解决Put与Compact速度差异的性能瓶颈问题。在LevelDB里面因为只有一个Memtable,如果Memtable满了却还来不及持久化,这个时候LevelDB将会减缓Put操作,导致整体性能下降。

看完上面这些介绍,相比LevelDB是不是觉得RocksDB彪悍的不可思议,很多该有的地方都有,该想的都想到了,简直不像在做引擎库,更像是在做产品。不过虽然RocksDB在性能上提升了不少,但在文件存储格式上跟LevelDB还是没什么变化的,稍微有点更新的只是RocksDB对原来LevelDB中sst文件预留下来的MetaBlock进行了具体利用。

个人觉得RocksDB尚未解决的地方:
依然是完全依赖于MANIFEST,一当该文件丢失,则整个数据库基本废掉。

合并上依然是整个文件载入,一些没用的Value将被多次的读入内存,如果这些Value很大的话,那没必要的内存占用将是一个可观的成本。


最新版本:7.0
RocksDB 7.0.0 现已于2022年3月中旬发布。

Bug修复
修复了当启用 memtable Bloom 过滤器 (memtable_prefix_bloom_size_ratio > 0) 时,批量 MultiGet 可能返回由 DeleteRange 删除的键的旧值的主要错误。
修复了更多 EventListener::OnTableFileCreated 调用的情况,状态为 OK,file_size==0,且没有保留 SST 文件。
修复了DB::GetMergeOperands().
修复并发事务提交和 memtable 切换导致的 2PC 写提交事务的数据丢失问题(#9571)。
修复了 NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL、NUM_DATA_BLOCKS_READ_PER_LEVEL 和 NUM_SST_READ_PER_LEVEL 统计信息,每个级别的每个 MultiGet 批次报告一次。
修复使用 DisableManualCompaction 取消手动压缩时的竞争条件,DB close 也可以取消手动压缩线程。
修复了 DBImpl::ResumeImpl() 和等待恢复完成的线程之间的 versions_ 数据竞争
修复了由刷新、传入写入和拍摄快照之间的竞争导致的错误,对使用这些竞争条件创建的快照查询可能会返回不正确的结果,例如重新显示已删除的数据。

性能改进
减少了构建在线 LSM 树一致性检查所使用的文件位置哈希表的开销,这可以提高某些工作负载的性能(参见#9351)。
切换到使用排序std::vector而不是std::map存储 blob 文件的元数据对象,可以提高某些工作负载的性能,尤其是当 blob 文件的数量很大时。
DisableManualCompaction() 不必等待计划的手动压缩在线程池中执行以取消作业。

行为改变
禁止 DBOptions.use_direct_io_for_flush_and_compaction == true 和 DBOptions.writable_file_max_buffer_size == 0 的组合,这种组合会导致 WritableFileWriter::Append() 永远循环,在直接 IO 中没有多大意义。
ReadOptions::total_order_seek不再影响 DB::Get(),这种交互已经过时,因为 RocksDB 已经能够检测当前的前缀提取器是否与用于生成表文件的前缀提取器兼容。

新特性
引入了BlockBasedTableOptions::detect_filter_construct_corruption在 Bloom Filter (format_version >= 5) 和 Ribbon Filter 构建期间检测损坏的选项。
改进了 SstDumpTool 以从表属性中读取比较器,并使用它来读取 SST 文件。
扩展了信息日志中的列族统计信息,还会记录 blob 文件中的垃圾总量和 blob 文件空间放大系数,还通过rocksdb.blob-stats DB 属性公开了 blob 文件空间放大器。
在 ch 中引入 APIrocksdb_create_dir_if_missing,调用底层文件系统的 CreateDirIfMissing API 来创建目录。
添加了最后一级和非最后一级读取统计信息:LAST_LEVEL_READ_* 、 NON_LAST_LEVEL_READ_*。
实验性:在 FSRandomAccessFile 中添加对新 API ReadAsync 的支持,以异步读取数据,并在 FileSystem 中添加 Poll API 以检查请求的读取请求是否已完成。ReadAsync 采用回调函数。轮询 API 检查读取 IO 请求的完成情况,并应调用回调函数来指示读取请求的完成。

7.0.0 版本还有海量公共 API 更改,更多详情可查看更新公告

RocksDB 7.6.0 现已于2022年9月下旬发布,更新内容如下:

新的功能
添加 prepopulate_blob_cache 到 ColumnFamilyOptions。
支持使用 blob 缓存的二级缓存。在创建 Blob 缓存时,用户可以通过配置 LRUCacheOptions 中的 secondary_cache 来设置二级 Blob 缓存。
当 blob 缓存和 block 缓存的 backing 缓存不同时,计费 blob 缓存的内存使用量。
改进 subcompaction 范围划分,使其可能更均匀。subcompaction 的更均匀分布将提高某些工作负载的压缩吞吐量。
添加 CompactionPri::kRoundRobin,这是一种 compaction picking 模式,它以循环方式处理所有带有 compact cursor 的文件。此功能从 7.5 开始可用。
为 user_defined_timestamp 提供了对 subcompactions 的支持。
添加了一个选项 memtable_protection_bytes_per_key,开启了每个 memtable entry 的校验和保护。
添加了特定于 blob 的缓存优先级 - bottom level。

Public API changes
删除了对 RateLimiter 的 Customizable 支持并删除了它的 CreateFromString () 和 Type () 函数。
CompactRangeOptions::exclusive_manual_compaction 现在默认为 false。这确保了 RocksDB 默认不会引入人为的并行限制。
Tiered Storage:更改 bottommost_temperture 为 last_level_temperture。保留旧选项名称仅用于迁移,请使用新选项。行为已更改为仅对 last_levelSST 文件 apply temperature 。
添加了一个名为 optimize_multiget_for_io 的新实验性 ReadOption flag,该标志在设置时尝试通过为多级的键生成 coroutines 来减少 MultiGet 延迟。

Bug 修复
修复从 7.4.0 开始的错误,即在一个 DB 上的任何 DropColumnFamily 之后,一些 fsync 操作可能被跳过,直到它被重新打开。这可能会导致断电时的数据丢失
修复一个错误,当用户配置一个包围它的结构时,GenericRateLimiter 可能会使用 SetBytesPerSecond () 动态地恢复设置的带宽,例如,使用 GetOptionsFromString () 配置一个引用现有 RateLimiter 对象的选项
修复 FIFOCompactionPicker::PickTTLCompactiontotal_size 计算可能导致下溢的错误
修复 hash linked list memtable 中的 race bug
修复了一个 bug,即 best_efforts_recovery 可能无法通过 mmap 读取来打开数据库
修复了压缩期间读取的 blob 会污染缓存的错误
修复了与 secondary_cache 一起使用时 LRUCache 中的 data race
修复了即使将 fill_cache 读取选项设置为 false ,迭代器读取的 blob 也会插入缓存的错误
修复了 CompressedSecondaryCache::SplitValueIntoChunks() 和 MergeChunksIntoValueTest 中 AllocateData() 导致的 segfault
修复了 BlobDB 中的一个 bug,即 inlined 和 blob 值的混合可能导致将不正确的值传递给 compaction filter(参阅#10391)
修复了压力测试中由 FaultInjectionSecondaryCache引起的内存泄露问题

性能改进
与其在每次读取操作中构建 FragmentedRangeTombstoneList,现在它被构建一次并存储在不可变的 memtable s 中。这提高了从不可变的 memtables 中查询 range tombstones 的速度。
将迭代器与集成的 BlobDB 实现结合使用时,现在会在迭代器的位置更改时立即释放 Blob cache handles。
如果设置了 optimize_multiget_for_io ReadOption flag,MultiGet 现在可以通过从多级读取 SST 文件中的 data blocks 来并行执行更多 IO。

更多详情可查看此处


官方主页:http://rocksdb.org/