RocksDBunordered_write特性全解析:放宽顺序保证以换取更高写入吞吐
【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb
unordered_write是 RocksDB 自 6.3 版本起提供的写路径优化选项,通过放宽"不可变快照"等顺序保证,显著提升高并发写入吞吐;在与 TransactionDB 的 WritePrepared 事务模型组合时,甚至可以在保持读自己写与不可变快照语义的同时获得 34%~42% 的吞吐提升。本文以官方博客文档为主体,结合当前仓库中的选项定义与实现源码,系统讲解该特性的设计动机、语义变化、两种配置方式、Benchmark 方法及其限制。
背景:RocksDB 默认提供的三大写路径保证
在默认配置下,RocksDB 的写路径向用户交付以下三组强保证:
- 原子读(Atomic reads):一个 WriteBatch 中的写入要么整体对读可见,要么整体不可见,不会出现"读到半个批次"的中间状态。
- 读自己写(Read-your-own-writes):当某个写线程的
Write调用返回后,该线程随后的读操作一定能看到自己刚才写入的数据。 - 不可变快照(Immutable Snapshots):读操作基于快照进行,快照可见的数据集不会受任何在途(in-flight)或未来写入的影响,是"冻结"的视图。
这组保证由写线程队列、sequence number 分配与发布顺序等机制共同支撑,代价是写路径上的串行化点与等待。对于写多读少、可容忍一定顺序松弛的应用,这份代价并非总是必要——这正是unordered_write存在的意义。
unordered_write:放宽哪些保证,保留哪些保证
在 include/rocksdb/options.h 中,unordered_write的注释对该特性做了权威定义(默认值为false,属于不可变选项,需在打开 DB 前设置):
// Setting unordered_write to true trades higher write throughput with // relaxing the immutability guarantee of snapshots. ... // If set to true, although Read-Your-Own-Write property is still provided, // the snapshot immutability property is relaxed: the writes issued after the // snapshot is obtained (with larger sequence numbers) will be still not // visible to the reads from that snapshot, however, there still might be // pending writes (with lower sequence number) that will change the state // visible to the snapshot after they are landed to the memtable. // // Default: false bool unordered_write = false;开启后语义变化如下:
| 保证 | 默认 RocksDB | unordered_write = true |
|---|---|---|
| 读自己写(Read-your-own-writes) | 提供 | 仍然提供 |
| 原子读(Atomic reads) | 提供 | 不再提供 |
| 不可变快照(Immutable Snapshots) | 提供 | 不再提供 |
注释中特别说明了一个细节:开启后,快照取得之后才发布(sequence number 更大)的写入,依然不会对快照可见;但可能存在 sequence number 更小的在途写入,它们在落地到 memtable 之后会改变该快照可见的状态——这就是"不可变性"被放宽的具体含义,也解释了为什么::Get在快照上的可重复性(repeatability)以及Iterator、MultiGet的一致时间点视图会被破坏(参见 include/rocksdb/db.h 中对该特性影响读 API 的说明)。
从底层实现看,unordered_write的收益来源可以从 db/db_impl/db_impl_write.cc 与 db/db_impl/db_impl_write.cc 的注释窥见一斑:该模式目前使用kDoPublishLastSeq的 sequence number 发布策略,写入在 memtable 中以无序(unordered)方式落地,从而减少写线程间的同步与等待。在 db/db_impl/db_impl.cc 附近也有"开启 unordered_write 时,key 以无序方式写入 memtable"的对应注释。
恢复不可变快照:WritePrepared 事务 + two_write_queues
如果应用无法接受上述松弛语义,官方给出的推荐方案是:使用 TransactionDB,并以WRITE_PREPARED写策略与two_write_queues=true组合使用。在 include/rocksdb/options.h 中明确写道:
Using TransactionDB with WRITE_PREPARED write policy and
two_write_queues=trueis one way to achieve immutable snapshots despite unordered_write.
其中two_write_queues的定义(include/rocksdb/options.h)指出:启用后写路径拆分为两个队列——一个用于disable_memtable(只写 WAL)的写入,一个用于同时写 memtable 的写入,避免 memtable 写入拖累其他写入,这也正是 MySQL 2PC 场景(只有串行的 commit 才写 memtable)的优化基础。
如何配置:两种使用方式
方式一:保持默认三保证(WritePrepared + two_write_queues)
原文档给出的完整示例,在保持原子读与不可变快照的同时获得吞吐提升:
DBOptions db_options; db_options.unordered_write = true; db_options.two_write_queues = true; DB* db; { TransactionDBOptions txn_db_options; txn_db_options.write_policy = TxnDBWritePolicy::WRITE_PREPARED; txn_db_options.skip_concurrency_control = true; TransactionDB* txn_db; TransactionDB::Open(options, txn_db_options, kDBPath, &txn_db); db = txn_db; } db->Write(...);其中write_policy与skip_concurrency_control均定义于 include/rocksdb/utilities/transaction_db.h:
write_policy:数据落库策略,默认为WRITE_COMMITTED(只写已提交数据);WRITE_PREPARED允许数据在 commit 阶段之前写入 DB,由 DB 提供区分已提交与未提交数据的机制,从而为无序写留出空间。skip_concurrency_control:当 TransactionDB 仅用于写排序(write ordering)而非并发控制时,可跳过内部锁管理;该选项的设计意图即"与DBOptions::unordered_write配合,在仅以 TransactionDB 承担写排序职责时使用"。
方式二:接受松弛保证(普通 DB 直开)
如果应用可以自行处理更宽松的顺序语义,则不需要 TransactionDB,直接开启unordered_write即可,且收益更大:
DBOptions db_options; db_options.unordered_write = true; DB* db; DB::Open(db_options, kDBPath, &db); db->Write(...);组合限制与校验:源码里的硬性约束
unordered_write并非可与所有选项随意组合,当前仓库在打开 DB 与每次写入时都会做严格校验:
- 在 db/db_impl/db_impl_open.cc 中,
DBImpl::SanitizeOptions拒绝以下组合并返回Status::InvalidArgument:unordered_write=true与allow_concurrent_memtable_write=false不兼容(无序写依赖并发 memtable 写入);unordered_write=true与enable_pipelined_write=true不兼容。
- 在 db/db_impl/db_impl_write.cc 中,
WriteImpl同样拒绝unordered_write与 pipelined write 的组合。 - 在 db/db_impl/db_impl_write.cc 中,
IngestWriteBatchWithIndex(通过WriteBatchWithIndex直接摄入)不支持unordered_write,会返回Status::NotSupported。
配置时请务必避开上述组合,否则 DB 打开或写入会直接报错。
基准测试:命令与结果
原文档给出了db_bench基准测试命令,核心参数含义如下:
TEST_TMPDIR=/dev/shm/ ~/db_bench --benchmarks=fillrandom --threads=32 --num=10000000 \ -max_write_buffer_number=16 --max_background_jobs=64 --batch_size=8 \ --writes=3000000 -level0_file_num_compaction_trigger=99999 \ --level0_slowdown_writes_trigger=99999 --level0_stop_writes_trigger=99999 \ -enable_pipelined_write=false -disable_auto_compactions \ --transaction_db=true --unordered_write=1 --disable_wal=0参数说明(部分为原文档未展开、结合db_bench惯例补充):
--benchmarks=fillrandom:随机 key 顺序填充写入;--threads=32:32 个并发写线程,用于放大写路径竞争;--num=10000000/--writes=3000000:key 空间与总写入次数;--batch_size=8:每批次写入 8 条记录;-max_write_buffer_number=16、--max_background_jobs=64:加大缓冲与后台任务配额,避免 flush/compaction 成为瓶颈;-level0_*_trigger=99999:将 L0 的 compaction 触发阈值抬到极大,等效于压测期间基本不触发 L0 compaction;-enable_pipelined_write=false:与unordered_write兼容的配置(见上文校验);-disable_auto_compactions:关闭自动 compaction,聚焦纯写入吞吐;--transaction_db=true --unordered_write=1:开启 TransactionDB 与unordered_write;--disable_wal=0:WAL 开启,用于对比带 WAL 与不带 WAL(--disable_wal=1)两种场景。
原文档报告的结果(相对于普通 RocksDB 的吞吐提升):
| 场景 | unordered_write=true+ WritePrepared 事务 | unordered_write=true(松弛保证) |
|---|---|---|
| WAL 开启 | +42% | +63% |
| WAL 关闭(No-WAL) | +34% | +131% |
即:保持三保证时提升 34%~42%;接受松弛语义时提升可扩大至 63%~131%。需要注意,这些数字来自官方博客发布时(RocksDB 6.3 时代)的特定软硬件与工作负载环境,具体收益会随机器、工作负载与版本变化,建议以本仓库版本重新运行db_bench实测为准。
总结
unordered_write的核心取舍非常清晰:用"不可变快照"与"原子读"的语义换取写路径上更少的同步开销。它适合写多读少、对顺序一致性要求不高、或能够通过业务层补偿顺序问题的场景;若必须保持原语义,则需叠加two_write_queues=true与 WritePrepared 事务(write_policy=WRITE_PREPARED、skip_concurrency_control=true)。配置时注意其与enable_pipelined_write、allow_concurrent_memtable_write=false及 WriteBatch 直接摄入等选项的硬性不兼容约束。相关选项定义可继续查阅 include/rocksdb/options.h 与 include/rocksdb/utilities/transaction_db.h,底层写路径实现可参考 db/db_impl/db_impl_write.cc。
【免费下载链接】rocksdbA library that provides an embeddable, persistent key-value store for fast storage.项目地址: https://gitcode.com/gh_mirrors/ro/rocksdb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考