原文发布于 quant67.com,转载请保留出处。
LevelDB 基线 把 DBImpl::Write、Get 与单线程 BackgroundCompaction 钉在 google/leveldb 1.23 源码上。Facebook 2012 年 fork 后,RocksDB 在 多核利用、嵌入场景、可观测性 三条线上持续演进;到 9.x 主线(本系列锚定 facebook/rocksdb 9.10.0 及相邻 9.x release,见 HISTORY.md),DBImpl 已拆成多个翻译单元,线程模型与 LevelDB 几乎无可比性。
本文是系列第 3 篇,不做性能 benchmark(未在本环境跑对比测试),只交付 架构 diff 地图 :每一项改动对应什么生产问题、落在哪些源文件、与 LevelDB 行为的边界差异。读完应能打开 RocksDB 树,从 include/rocksdb/db.h 的 API 跳到 db/db_impl/db_impl_write.cc 的 WriteImpl,而不在 ColumnFamilySet 与 WriteThread 之间迷路。
本文是「RocksDB 内核机制」系列第 3 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 2 篇 · LevelDB 基线 单线程 leveled 骨架 第 3 篇 · RocksDB 架构演进 LevelDB vs RocksDB diff 地图 第 4 篇 · WAL 与 WriteBatch WriteImpl逐步展开第 5 篇 · MemTable 与 Flush FlushJob与 immutable 队列
版本锚定 :RocksDB 9.x (源码
post/db/rocksdb/reproduce/rocksdb/);LevelDB 1.23(上篇基线)。环境说明 :WSL2;本篇机制来自 RocksDB Wiki RocksDB vs LevelDB 、
HISTORY.md与源码目录结构,不引用未执行的吞吐数据。
一、fork 动机:LevelDB 的三条硬边界
LevelDB 在 2011 年满足 Chrome IndexedDB 等场景的 嵌入式、顺序写优先 需求。Facebook 迁移到 MyRocks 等 workload 时,三条边界最先撞墙:
- CPU :单
BackgroundCompaction无法喂饱多核与高速 SSD。 - 命名空间:同一 DB 内无法隔离多组 comparator / compaction 选项(Column Family 需求)。
- 可运维性:缺少结构化 Statistics、I/O 限流、Direct I/O,大状态 DB 难以诊断与控资源。
RocksDB 选择 保留 LSM 磁盘语义 (WAL、SST 不可变、MANIFEST、InternalKey + sequence),在 DBImpl 外围加线程池、CF 抽象与监控钩子。Wiki 页 RocksDB vs LevelDB 是官方 diff 清单;下表在本系列语境下补充 源码路径 与 后续篇目。
二、LevelDB vs RocksDB 总览 diff 表
| 维度 | LevelDB 1.23 | RocksDB 9.x | 主要源码 / 文档 |
|---|---|---|---|
| 后台 flush | 与 compaction 共用 1 个 Env::Schedule |
独立 FlushJob,可并行多个 immutable memtable |
db/flush_job.*、db/flush_scheduler.* |
| 后台 compaction | 单线程 DoCompactionWork |
CompactionJob + 线程池,多 job 并发 |
db/compaction/compaction_job.* |
| 逻辑分区 | 无 | Column Family,共享 WAL、独立 LSM | db/column_family.* |
| 并发写 MemTable | 队首线程 group commit | WriteThread 流水线,可 allow_concurrent_memtable_write |
db/write_thread.*、db_impl_write.cc |
| I/O 限流 | 无 | RateLimiter(flush/compaction/read/write 分类) |
util/rate_limiter.* |
| 绕过 OS 页缓存 | 依赖 mmap / read | Direct I/O 可选(use_direct_reads/writes) |
Wiki Direct IO ;env/* |
| 监控 | info_log 文本 |
Statistics 、PerfContext、持久化 stats CF |
monitoring/*、include/rocksdb/statistics.h |
| Compaction 策略 | Leveled | Leveled + Universal + FIFO + TTL 等 | db/compaction/* |
| 事务 / 备份 | 无 | OptimisticTransactionDB、Checkpoint、BackupEngine |
utilities/* |
| 过滤器 | Bloom | Bloom + Ribbon、Partitioned Filter | table/block_based/* |
| API 面 | DB |
DB、ColumnFamilyHandle、WriteBatchWithIndex、... |
include/rocksdb/*.h |
以下各节展开 对上表右侧 的机制,不重复 LevelDB 已讲过的 WAL/MemTable 最小语义。
三、源码布局:从单文件 DBImpl 到子系统目录
LevelDB 的核心实现在 db/db_impl.cc(约 1500 行)。RocksDB 9.x 将 DBImpl 拆散以便并行维护:
text
db/db_impl/
db_impl.cc # 构造、Open、Close、属性
db_impl_write.cc # WriteImpl、WAL、WriteThread 交互
db_impl_compaction_flush.cc
db_impl_read.cc # Get、MultiGet
db_impl_open.cc # 恢复、MANIFEST replay
db/column_family.*
db/flush_job.*
db/compaction/compaction_job.*
db/write_thread.*
db/version_set.*
阅读顺序建议:db.h API → db_impl_write.cc::Write → WriteImpl → MemTableList → FlushJob (第 4--5 篇);读路径走 db_impl_read.cc(第 7 篇)。
RocksDB 仍保留 LevelDB 版权头与 ROCKSDB_NAMESPACE 命名空间;许多类名(VersionSet、WriteBatch、TableCache)直接延续,降低从 LevelDB 跳转的认知成本。
四、学术谱系:从单线程 LevelDB 到 bLSM
LevelDB 与 RocksDB 的 diff 不是「重写 LSM」,而是 在同一磁盘语义上并行化后台 。读 diff 表时应能回答:多线程 compaction 在文献里对应什么、RocksDB 相对论文改了什么。
| 阶段 | 系统 / 文献 | 相对前一代 |
|---|---|---|
| 1996 | O'Neil et al., LSM-Tree | 定义顺序写 + 后台归并(第 1 篇 §2.1) |
| 2011 | LevelDB(Dean & Ghemawat) | 单进程 leveled + 单后台 worker (第 2 篇) |
| 2012 | bLSM(Sears & Ramanan, SIGMOD) | Many-core compaction、控制 L0 run 以减 stall |
| 2012+ | RocksDB fork | 线程池 flush/compaction、CF、RateLimiter、Statistics(本篇 diff 表) |
工程结论 :bLSM 论文证明 并行 compaction 可缩短 stall 窗口 ;RocksDB 的 CompactionJob + Env 线程池 + NeedSpeedupCompaction() 是 工业实现路径 ,与 bLSM 源码 不一一对应(以 RocksDB 9.x 源码为准)。
4.1 工程间隙
| 论文 / LevelDB 假设 | MyRocks / Flink / TiKV |
|---|---|
| 单机、单 CF | 多 CF、多 subtask 目录 |
| 单 BG 线程瓶颈 | 磁盘带宽与 checkpoint 读 成新瓶颈 |
| 无 RateLimiter | 云盘 IOPS 配额 + 引擎内 limiter 叠层 |
4.2 开放问题
- Disaggregated compaction :compaction 是否应远程执行以换本地写带宽?产品与 NSDI/OSDI 存储分离论文并存(db-frontier/14)。
- CF 扩展 vs 多 DB :Column Family 共享 WAL 的 stall 传染 是否在 skew 下劣于多进程?社区实践分裂,无顶会统一结论。
- Ribbon / 新 filter 默认化 :读路径 CPU 与 compaction 构建成本如何影响 嵌入 JVM 场景?以 release note 为准(第 9 篇)。
五、多线程 Flush 与 Compaction
LevelDB 回顾
单个 BackgroundCompaction:若 imm_ != nullptr 则 CompactMemTable(),否则 PickCompaction()。flush 与 level compaction 互斥。
RocksDB 演进
FlushScheduler/FlushJob:MemTable 写满后进入MemTableList的 immutable 队列;多个 flush 可并行(受max_background_flushes限制),各自产出 L0 SST。CompactionJob:在max_background_compactions线程池中运行;picker(CompactionPicker)可为 leveled/universal/fifo 选择不同输入集。- 解耦:flush 瓶颈不再直接阻塞所有 level compaction------但 L0 文件过多仍触发 write stall(机制延续 LevelDB,阈值与状态机更复杂,第 11 篇)。
生产含义:Flink 状态后端在 checkpoint 对齐后大量 flush,若 max_background_flushes 过小,immutable memtable 队列变长------与 stream/12 讨论的 checkpoint I/O 争抢直接相关(第 16 篇量化边界,本篇不伪造耗时)。
六、Column Family:共享 WAL,独立 LSM
Column Family(CF)是 RocksDB 相对 LevelDB 最大的结构扩展。每个 CF 拥有:
- 独立的 MemTable 链、SST 层级、
CompactionPicker选项; - 共享同一 WAL 与同一
DBImpl进程; - 独立
ColumnFamilyHandle供Put/Get/NewIterator指定。
默认 CF 名 default(db_impl.cc 中 kDefaultColumnFamilyName)。ColumnFamilyDescriptor 在 Open 时注册;运行时 CreateColumnFamily 追加新 LSM 树。
TiKV、Flink 用 CF 隔离 不同 state 变量或模块 (例如 Flink 为 ValueState、MapState 分配不同 CF)。第 13 篇会讲 ColumnFamilyData 与 WAL 回收的交互;本篇只记 边界:LevelDB 的「整库一棵 LSM」在 RocksDB 变为「整库一棵 WAL + 多棵 LSM」。
七、WriteThread 与并发 MemTable 写
LevelDB 用 writers_ deque + 条件变量:仅队首线程写 WAL 与 mem_。RocksDB 引入 WriteThread (db/write_thread.h):
- 写线程先 加入 write group,由 group leader 负责 WAL;
- 配置
DBOptions::allow_concurrent_memtable_write = true时,group 内线程可 并行InsertInto各自负责的 MemTable 分区(配合 skip list 并发插入支持); - 可选 pipelined write (
enable_pipelined_write):WAL 与 MemTable 阶段重叠,提高写吞吐。
对外 API 仍是一个 DB::Write:
cpp
Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) {
// ...
s = WriteImpl(write_options, my_batch, /*callback=*/nullptr,
/*user_write_cb=*/nullptr, /*log_used=*/nullptr);
return s;
}
(摘自 RocksDB 9.x db/db_impl/db_impl_write.cc。)
WriteImpl 内部协调:WriteThread 入队 → 分配 sequence → WAL(可能 ConcurrentWriteToWAL)→ 各 CF 的 MemTable → 更新 VersionSet 的 last sequence。第 4 篇按调用顺序逐步展开;与 LevelDB 的 diff 核心是 WriteThread 状态机 替代简单 writers_ 队列。
八、RateLimiter:区分前台与后台 I/O
RocksDB 的 RateLimiter(NewGenericRateLimiter)按 I/O 优先级 分配字节令牌:
Env::IO_USER:前台写、用户线程触发的读;Env::IO_HIGH/IO_MID/IO_LOW:compaction、flush 等后台类。
Wiki Rate Limiter 说明:rate_bytes_per_sec 为上限,可 auto_tuned 根据 backlog 调节。LevelDB 无等价物;compaction 与前台写争抢磁盘时只能依赖 OS 调度。
绑定路径:compaction/flush 的 WritableFileWriter 在写 SST 前向 limiter Request();DBOptions::rate_limiter 指针注入全库。第 12 篇用 db_bench 对比限流开/关(实验在 reproduce/,未跑则不写数字结论)。
九、Direct I/O 与压缩
Direct I/O
默认路径经 page cache (mmap 或 read)。大 DB 下 compaction 读 SST 与用户读共享 cache,易出现 双缓冲 (cache + compaction 缓冲)。use_direct_reads / use_direct_writes(及 CF 级 TableOptions)让 SST I/O 绕过 OS cache,配合 block_cache 由 RocksDB 自行管理热点块。
Wiki Direct IO 强调:需块对齐、与 block_size 配合;不是所有文件系统组合都受益。LevelDB 无 first-class 支持。
压缩
RocksDB 支持 Snappy、Zlib、LZ4、ZSTD 等 per-level 压缩策略(compression_per_level)。LevelDB 主要 Snappy。压缩影响 写放大 (CPU 换空间)与 读路径 CPU ;不是架构 diff 的核心,但出现在 TableBuilder 与 FlushJob 配置中。
十、Statistics 与可观测性
LevelDB 依赖 options.info_log 人工 grep。RocksDB 提供:
| 机制 | 用途 |
|---|---|
Statistics |
计数器:rocksdb.block.cache.hit/miss、rocksdb.compaction.key.drop 等 |
PerfContext |
单次 Get/Iterator 的微结构耗时 |
IOStatsContext |
线程级 I/O 统计 |
| Persistent stats CF | 历史 stats 落盘(___rocksdb_stats_history___ CF) |
DB::GetProperty("rocksdb.stats") 文本摘要与 LOG 中的 compaction 事件,是 第 17 篇 排查 L0 堆积的入口。Statistics 不改变 LSM 语义,但使 stall 与放大 从「感觉慢」变为可核对指标。
十一、读路径与 SuperVersion(预告)
LevelDB 在 Get 里 Ref mem_、imm_、Version::current()。RocksDB 引入 SuperVersion :原子快照 所有 CF 的 MemTable 列表 + 当前 Version 指针,避免读路径长时间持 DB mutex。
SuperVersion 切换在 flush/compaction 提交 LogAndApply 后;旧 SuperVersion 引用计数归零后回收 immutable memtable。第 7 篇 Get 篇会沿 db_impl_read.cc 展开;本篇只标记 diff:多 CF + SuperVersion 使读路径比 LevelDB 多一层间接,但单次点查逻辑仍为 MemTable 链 → L0...Ln。
十二、Compaction 策略扩展
LevelDB 仅 leveled compaction (leveldb::Options::compaction_style implicit)。RocksDB 增加:
- Universal :减少写放大,适合写-heavy、可容忍读放大(Wiki Universal Compaction);
- FIFO:类似队列截断,适合 TTL 纯插入流;
- TTL 、BlobDB(大 value 外置)等扩展。
CompactionPicker 接口化;leveled 不变式(L0 重叠、L1+ 不重叠)仍是默认生产配置,与 storage/31 讨论一致。策略 diff 的源码主战场是 db/compaction/*(第 10--11 篇)。
十三、API 与工具面增量(选型视角)
RocksDB 在 LevelDB DB 之上增加的常用能力(不全列,见 include/rocksdb/db.h):
- Checkpoint / Backup:硬链接 SST 快照(Flink 增量 checkpoint 依赖不可变 SST 语义);
IngestExternalFile:外部生成 SST bulk load;MergeOperator、CompactionFilter:读时合并与 compaction 时过滤;Snapshot、TransactionDB:嵌入场景上的 MVCC 与 OCC。
LevelDB 的 WriteBatch、Snapshot、Iterator 语义保留;迁移 RocksDB 的代码通常只需改 namespace 与 Options,直到需要 CF 或多线程调优。
十四、diff 地图与系列后续篇目
| diff 主题 | 本系列展开篇目 |
|---|---|
| WAL + WriteBatch + group commit | 第 4 篇 |
| MemTable + FlushJob | 第 5 篇 |
| SST + MANIFEST + VersionSet | 第 6 篇 |
| Get + Snapshot + SuperVersion | 第 7 篇 |
| Iterator + Merge | 第 8 篇 |
| Block cache + Bloom/Ribbon | 第 9 篇 |
| Leveled / Universal + stall | 第 10--11 篇 |
| RateLimiter + 并发 compaction | 第 12 篇 |
| Column Family | 第 13 篇 |
| Flink / TiKV 嵌入 | 第 16 篇 |
十五、小结
RocksDB 不是重写 LSM,而是在 LevelDB 骨架上 并行化后台、拆分 CF、暴露监控与限流。理解 diff 地图后,阅读 9.x 源码应带着三个问题:这条路径 LevelDB 有没有?若有,是单线程还是线程池?WAL 是整库共享还是 per-CF 逻辑隔离?
下一篇进入写路径细节:WAL record 格式、WriteBatch 原子性、WriteImpl 里 WAL 与 MemTable 的提交顺序------直接对接本系列实验台账中的 WAL 目录观测。
参考资料
- RocksDB Wiki, RocksDB vs LevelDB(官方 diff 清单)。
- Sears & Ramanan, bLSM, SIGMOD 2012(多核 compaction;A 级)。
- O'Neil et al., LSM-Tree , Acta Informatica 1996(LSM 奠基;A 级,详见 第 1 篇)。
- RocksDB Wiki, Architecture / Rate Limiter / Direct IO。
- RocksDB 源码
facebook/rocksdb9.x:db/db_impl/db_impl_write.cc(Write、WriteImpl)、db/write_thread.*、db/column_family.*、db/flush_job.*、db/compaction/compaction_job.*。 - RocksDB
HISTORY.md(9.x release 行为变更边界)。 - Google LevelDB 1.23
db/db_impl.cc(上篇基线)。 - 本系列第 2 篇、第 4 篇。
- storage/31 LSM 调优(compaction 策略与放大)。
返回 系列目录 | 上一篇:LevelDB 基线 | 下一篇:WAL 与 WriteBatch