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
拆散以便并行维护:
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 篇)。
flowchart LR
subgraph leveldb["LevelDB"]
BG1["Single BG thread"]
BG1 --> FL1["CompactMemTable"]
BG1 --> CP1["DoCompactionWork"]
end
subgraph rocksdb["RocksDB 9.x"]
POOL["ThreadPool Env"]
POOL --> FJ["FlushJob x N"]
POOL --> CJ["CompactionJob x M"]
end
生产含义: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:
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 地图与系列后续篇目
flowchart TB
L["LevelDB 1.23<br/>single BG + no CF"]
R["RocksDB 9.x"]
L -->|"WriteThread CF"| R
L -->|"FlushJob CompactionJob"| R
L -->|"RateLimiter Statistics"| R
R --> P4["Ch04 WAL WriteImpl"]
R --> P5["Ch05 MemTable Flush"]
R --> P10["Ch10-12 Compaction Stall"]
R --> P13["Ch13 Column Family"]
R --> P16["Ch16 Flink TiKV embed"]
| 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
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【RocksDB 内核机制】并发 Compaction 与 Rate Limiter
从 CompactionJob 的 Prepare/Run/Install 与 subcompaction 并行,到 Env 高低优先级线程池、MaybeScheduleFlushOrCompaction 调度,以及 GenericRateLimiter 的 kWritesOnly 与 DelayWrite 的争抢关系;exp12 三路中位数对比方法论。
【RocksDB 内核机制】LevelDB · WAL · Compaction · Column Family · 生产嵌入
补全存储引擎三角最后一角:从 LevelDB 基线与 RocksDB 架构演进,到 WAL/MemTable/SST 写路径、Get/Iterator 读路径、Leveled/Universal compaction 与 write stall,再到 Column Family、事务、Checkpoint 与 Flink/TiKV 嵌入对照。
【RocksDB 内核机制】LSM 生态全景:存储引擎三角与 RocksDB 生态位
闭合存储引擎三角的最后一角:对照 PG B-Tree 与列存 scan,定位 LSM 写优化引擎在 TiKV、Flink、Kafka Streams、ClickHouse Embedded 中的嵌入方式,并划分本系列与 lsm-tree DIY、storage/31–32、stream/12 的分工边界。
【RocksDB 内核机制】Column Family:共享 WAL 与独立 LSM
从 RocksDB 3.0 引入的 Column Family 出发,拆解共享 WAL、独立 MemTable/SST/Version 链、`ColumnFamilyHandle` 生命周期与 DBOptions/ColumnFamilyOptions 分层;并对照 Flink 多 state 变量到 CF 的映射边界。