读完 PostgreSQL
内核系列 的行存 B-Tree + MVCC,以及 列存引擎系列
的读优化 scan 之后,写优化 LSM 往往仍停在两个入口:lsm-tree DIY 五篇 的 toy
engine,或 storage/32
RocksDB 实践 的参数模板。与此同时,TiKV、Flink
EmbeddedRocksDBStateBackend、Kafka Streams
本地状态目录里实际跑的是 RocksDB——stream-processing
第 12 篇 从 Flink 视角讲了 KeyGroup 与增量
checkpoint,但没有按
DBImpl::Write、CompactionJob、write
stall 的源码路径系统拆解。
本文是「RocksDB 内核机制」系列第 1 篇。目标不是重复 LSM 三种放大的推导(见 lsm-tree 第 1 篇 与 storage/31),而是回答三个定位问题:
- 在 B-Tree 行存 / 列存 scan / LSM 写优化 构成的存储三角里,RocksDB 占哪一角、适合什么负载?
- TiKV、Flink、Kafka Streams、ClickHouse Embedded RocksDB 各自把 RocksDB 嵌在什么层次?
- 本系列 18 篇与相邻系列的分工边界在哪里?
本文是「RocksDB 内核机制」系列第 1 篇(共 18 篇)。→ 系列目录
篇目 核心内容 第 1 篇 · LSM 生态全景 存储三角、生产嵌入生态位、系列地图 第 2 篇 · LevelDB 基线 单线程 leveled、 DBImpl::Write/Get骨架第 3 篇 · RocksDB 架构演进 相对 LevelDB 的 diff 地图 第 4 篇 · WAL 与 WriteBatch 持久化与原子批写
版本锚定:RocksDB 9.x(本系列源码路径与 Wiki);LevelDB 1.23(对照基线);Flink 1.20+(第 16 篇引用边界)。
环境说明:本机 WSL2(内核 6.6.x),RocksDB 源码位于
post/db/rocksdb/reproduce/rocksdb/。本篇以架构与生态定位为主,不粘贴未执行的db_bench数字;机制结论来自 RocksDB Wiki、LevelDB/RocksDB 源码与官方嵌入文档。
一、存储引擎三角:三种优化方向
关系型 OLTP、分析型 scan、嵌入式 KV 日志层面对的不是同一组瓶颈。把它们粗分为三角,便于选型和阅读路径对齐——三角不是互斥,而是 默认优化方向 不同。
flowchart TB
subgraph triangle["Storage Engine Triangle"]
PG["Row OLTP<br/>B-Tree + MVCC"]
COL["Columnar Scan<br/>Vectorized Read"]
LSM["Write-Optimized LSM<br/>RocksDB / LevelDB"]
end
PG --- COL
COL --- LSM
LSM --- PG
| 角 | 代表实现 | 前台写路径 | 前台读路径 | 典型场景 |
|---|---|---|---|---|
| 行存 B-Tree | PostgreSQL Heap + B-Tree、InnoDB | 随机页写 + WAL | 索引定位 + 缓冲池命中 | OLTP、事务、等值/范围查询均衡 |
| 列存 scan | ClickHouse MergeTree、DuckDB Columnar | 批量 part 追加 + 后台 merge | 列块向量化 scan + 谓词下推 | OLAP、报表、聚合为主 |
| 写优化 LSM | LevelDB、RocksDB | WAL 顺序追加 + MemTable 内存写 | MemTable → L0…Ln 层级查找 | 写密集 KV、流状态、分布式 KV 本地引擎 |
行存 B-Tree 角
PostgreSQL
内核 已经按源码路径讲过:Backend
进程、Buffer Manager 页缓存、B-Tree 索引、WAL
与 MVCC 快照。B-Tree 的前台写路径本质是 定位叶子页 +
原地更新(或分裂),至少一次随机读加一次随机写。读路径在索引命中时接近
\(O(\log n)\)
次页访问,配合缓冲池可以压到内存。
这一角的优势是 事务语义、范围扫描、读延迟稳定。代价是写密集场景下随机 I/O 成为瓶颈——这正是 LSM-Tree 要解决的问题(见下一节谱系)。
列存 scan 角
列存引擎系列
从 MergeTree part
布局讲到向量化执行:数据按列存储、按批读取,聚合与 filter 在
CPU cache 友好的列块上完成。写路径通常是 追加新 part
+ 后台 merge,与 LSM
的「只追加、后台归并」有结构相似性,但
列存优化的是分析 scan,不是通用点查
KV;也不提供 RocksDB 那种嵌入式
Put/Get API。MonetDB/X100(Boncz
et al., SIGMOD 2005)与后续向量化 OLAP 论文定义的是
scan 向量化 范式,与 LSM 的 点查 +
范围 Iterator 优化目标不同(columnar-engine/04)。
LSM 写优化角
lsm-tree 第 1 篇 推导了写放大 \(WA\)、读放大 \(RA\)、空间放大 \(SA\) 的定义与 leveled compaction 的数量级。核心机制不变:
\[ \text{前台写} \approx \text{WAL append} + \text{MemTable insert} \]
Compaction 在后台把随机写「摊平」成顺序 I/O,但同一份用户数据可能被多次写入磁盘。RocksDB 是这一角在 生产环境 的默认实现之一:Facebook 2012 年 fork LevelDB,面向多核、SSD、大状态嵌入场景持续演进(RocksDB Wiki, Architecture)。
二、学术谱系:从 LSM 论文到 RocksDB
工程三角背后有一条可核对的 论文—系统 链。读本系列时,至少应能回答:今天 leveled compaction 从哪来、与 tiering 争论什么、RocksDB 相对论文假设改了什么。
2.1 奠基:LSM-Tree 与 B-Tree 分工
| 工作 | 会议/期刊 | 本文引用点 |
|---|---|---|
| O’Neil et al., The Log-Structured Merge-Tree (LSM-Tree) | Acta Informatica, 1996 | 定义 先日志、后排序、后台归并;写路径顺序化;\(WA\)/\(RA\) 权衡的原始框架 |
| Graefe, Revolution in Database Technology 等 LSM 综述 | 多篇;见 storage/31 | 把 LSM 与 B-Tree 并列为 访问方法 设计空间 |
B-Tree 侧的经典参照是 Bayer & McCreight (1972) 与 System R 的索引结构(postgresql-kernel 系列从 B-Tree 页讲)。不是「B-Tree 过时、LSM 全面更好」——O’Neil 论文针对的是 写比例高、读可接受多路归并 的工作负载;OLTP 点查 + 复杂事务仍以 B-Tree 为主流。
2.2 生产分叉:LevelDB → RocksDB
| 阶段 | 系统 | 相对前一代改了什么 |
|---|---|---|
| 2011 | LevelDB(Dean & Ghemawat, Google) | 单进程 leveled compaction、SST + MANIFEST;单后台 compaction 线程(第 2 篇) |
| 2012+ | RocksDB(Facebook fork) | 多线程 flush/compaction、ColumnFamily、并发
MemTable 写、RateLimiter、Direct I/O(第
3 篇) |
| 2014+ | bLSM 等工业变体 | 论文 Many-core compaction(Sears & Ramanan, SIGMOD 2012)影响「stall 与并发 compaction」思路;RocksDB 实现路径以 Wiki/源码为准,不一一对应 |
LevelDB 的 doc/impl.md 与 RocksDB Wiki
RocksDB vs LevelDB 是 A 级 工程
diff 文档;本系列第 2–3 篇按源码钉住差异,不在此重复 API
列表。
2.3 争论:Leveled vs Tiering(Size-Tiered)
Leveled(LevelDB/RocksDB 默认)与 tiering(Size-tiered / 大层合并)在文献与 storage/31 中已有 \(WA\)/\(RA\) 公式对照。顶会侧的代表性讨论:
| 立场 | 代表文献 | 核心主张 |
|---|---|---|
| Leveled 读友好 | LevelDB 设计;Dayan & Idreos, Dostoevsky (SIGMOD 2018) | 控制 sorted run 数;L1+ 不重叠使点查每层 O(1) 个 SST;\(WA\) 随 size ratio \(T\) 与层数上升 |
| Tiering 写友好 | Cassandra / HBase 传统 STCS;Dayan & Idreos, The Log-Structured Merge-Bush (SIGMOD 2017) | 同层多 run,减少层间反复重写;更低 \(WA\),但 run 数多 → \(RA\) 上升 |
| 中间地带 | Dostoevsky;RocksDB Universal compaction | 在 leveled/tiering 间插值;生产用
CompactionStyle::kCompactionStyleUniversal(第 11
篇) |
工程结论(有文献支撑,非口号):没有 universally optimal 的 compaction;Flink state、TiKV 等 写突发 + 读 state 混合负载,常在 leveled 与 universal 之间 POC(参数见 storage/32,机制见本系列 10–12 篇)。
2.4 RUM 与「三种放大」
Data et al., RUM Conjecture (SIGMOD 2016) 把读开销、更新开销、内存(或空间)表述为 三选二 的不可能三角,与 LSM 社区的 \(WA\)/\(RA\)/\(SA\) 口径不同但 直觉同构:LSM 用 空间与读 换 写吞吐。读 LSM 论文或调 compaction 时,应同时看 storage/31 第一节 的公式与 RUM 表述,避免把「放大」当成纯经验参数。
2.5 工程间隙:论文实验 vs 嵌入生产
| 论文/教程常见假设 | TiKV / Flink 现实 |
|---|---|
| 单机 SSD、长时间 steady-state | 云盘 IOPS 上限、checkpoint 突发读 |
| 均匀随机 key | KeyGroup 倾斜、Region 热点 |
| 单 CF、单进程 | 多 CF、JVM + JNI + 多 subtask 目录 |
| 忽略 tail latency | barrier 对齐、metastore 与 state IO 叠加 |
本系列 第 16–17 篇 从内核侧写 stall 与排查;不 替代 TiKV/Flink 端到端 SLA 论文或 benchmark(如 db-frontier/12 中的 HTAP 基准)。
2.6 开放问题(阅读本系列的锚点)
- 自适应 compaction:能否在 无离线 DBA 前提下,按 workload 在 leveled/tiering 间切换且保证 tail?Learned / 反馈控制 compaction 仍在研究线与工业实验(B 级:各厂商 blog;A 级:Neo/Bao 系思路可对照 db-frontier/03 的「反馈环」,但对象不同)。
- LSM 上的 HTAP:行存副本(TiKV)+ 列存副本(TiFlash)是否优于 统一 LSM?社区无单一答案——见 db-frontier/12 与 第 18 篇。
- Disaggregated storage:NVMe-oF / CXL 下 WAL 与 SST 是否仍应本地?NSDI/OSDI 近年 storage disaggregation 论文与 RocksDB Remote Compaction 等 产品方向 并存,版本边界以 release note 为准。
三、RocksDB 在 LSM 生态中的位置
LevelDB 是 教学级基线:单后台 compaction 线程、无 Column Family、API 极简。RocksDB 保留 LSM 骨架(WAL、MemTable、SSTable、MANIFEST、Leveled/Universal compaction),在并发、可观测性、嵌入特性上大幅扩展。下面四个生产系统说明「谁在什么层次嵌 RocksDB」。
flowchart LR
subgraph embed["RocksDB Embedded In"]
TIKV["TiKV<br/>Region + Raft"]
FLINK["Flink<br/>Keyed State"]
KAFKA["Kafka Streams<br/>Local State Dir"]
CH["ClickHouse<br/>EmbeddedRocksDB"]
end
ROCKS["RocksDB Engine"]
TIKV --> ROCKS
FLINK --> ROCKS
KAFKA --> ROCKS
CH --> ROCKS
| 系统 | RocksDB 角色 | key 空间特点 | 与本系列相关篇目 |
|---|---|---|---|
| TiKV | 每个 Region 的本地 KV 引擎(Raft 之上) | 带 MVCC 时间戳的内部 key 编码 | 第 13–14、16、18 篇 |
| Flink | 每个 subtask 独立 RocksDB 实例 | KeyGroup 前缀 + 多 Column Family | 第 5、13、16 篇;对读 stream/12 |
| Kafka Streams | 任务目录下
rocksdb 子目录 |
changelog topic 与 state store 映射 | 第 16 篇对照 |
| ClickHouse | EmbeddedRocksDB
等场景的字典/元数据 KV |
与 MergeTree 主存储分工 | 第 18 篇选型地图 |
TiKV:分布式 KV 的本地 LSM 层
TiKV 在 Raft 复制的 单个 Region 内使用 RocksDB(或兼容 LSM 实现)持久化 key-value。分布式事务、Percolator 模型、Raft log 不在 RocksDB 内核系列展开——本系列只覆盖 单个 DB 实例 内的 WAL、MemTable、compaction、Column Family。读 TiKV 文档时,应把 RocksDB 看作 Region 本地的有序 KV 引擎,上层再叠 MVCC 与 coprocessor。
Flink:流状态的后端
stream-processing
第 12 篇
已说明:EmbeddedRocksDBStateBackend 为每个需要
KeyedState 的 subtask 打开独立 RocksDB,Column Family
对应不同 state 变量,KeyGroup 决定 key 前缀。增量 checkpoint
上传自上次 checkpoint 以来 新生成的
SST——这直接依赖 RocksDB 的 不可变 SST
文件 语义与 MANIFEST 版本管理。
本系列第 16 篇会从 内核侧 补全:为何 L0
堆积会拖慢 checkpoint 期间的读、为何
write_buffer_size 与 flush 线程数影响 barrier
对齐后的状态持久化延迟。不在此重复 Flink
运行时调度全文。
Kafka Streams 与 ClickHouse Embedded
Kafka Streams 将 state store 落在本地 RocksDB 目录,通过 changelog topic 做容错;机制上与 Flink 同属 嵌入 LSM + 外部日志复制,但 key 编码与 checkpoint 模型不同(第 16 篇用对照表收束)。
ClickHouse 主存储是 MergeTree(列存角),RocksDB 出现在 EmbeddedRocksDB 等 辅助 KV 场景——选型上属于「列存为主、点查 KV 为辅」,第 18 篇阅读地图会把它与纯 RocksDB 负载区分。
四、与相邻系列的分工边界
读者容易在三处内容之间来回跳转。下表明确 谁写什么,避免重复劳动。
| 话题 | 本系列(RocksDB 内核) | 相邻系列 |
|---|---|---|
| LSM 三种放大公式与直觉 | 引用,不重复推导 | lsm-tree/01、storage/31 |
| 200+ 参数调优模板 | 不展开 | storage/32 |
| 亲手实现 WAL/MemTable/SST | 对照引用 | lsm-tree 全系列 |
| Flink KeyGroup / 增量 checkpoint 作业侧 | 第 16 篇内核侧 | stream/12–13 |
| MPP 查询 spill 到磁盘 | 不展开 | query-engine/16 |
| HTAP / TiKV Raft 全文 | 第 18 篇续作入口 | db-frontier/12 |
与 lsm-tree DIY 的关系
lsm-tree 系列 用
C/Rust 从零实现可运行引擎,价值在于
亲手验证
InternalKey、VersionEdit、最小堆归并等设计。本系列假设读者已理解这些概念,转而阅读
RocksDB 9.x 源码 中的
DBImpl、FlushJob、CompactionJob、SuperVersion。
对照阅读建议:lsm-tree 第 2 篇(WAL + MemTable)↔︎ 本系列第 4–5 篇;lsm-tree 第 3–4 篇(SST + Compaction)↔︎ 本系列第 6、10–11 篇。
与 storage/31–32 的关系
storage/31
从工程角度讨论 leveled/universal 策略与 Bloom 分配;storage/32
给出面向生产的 Options 模板。本系列 不讲参数
cookbook,而是讲 机制如何驱动 stall
与放大(第 10–12、17 篇),使读者在读 storage/32
时能对应到 DBImpl::WriteImpl 里的
WriteThread 与
CompactionPicker。
与 stream-processing/12 的关系
stream/12 回答「Flink 作业里 RocksDB 目录长什么样、checkpoint 传什么」。本系列回答「传下来的 SST 在引擎里如何产生、compaction 如何与前台写争抢 I/O」。两篇 正交:stream/12 偏作业运维,本系列偏内核路径。
五、本系列 18 篇阅读地图
全系列按 基线 → 写路径 → 读路径 → compaction → 高级特性 → 生产嵌入 组织。依赖关系如下(节点内为篇号简称):
flowchart TD
A["01 LSM Ecosystem"] --> B["02 LevelDB Baseline"]
B --> C["03 RocksDB Architecture"]
C --> D["04 WAL WriteBatch"]
D --> E["05 MemTable Flush"]
E --> F["06 SST MANIFEST"]
F --> G["07 Get Snapshot"]
G --> H["08 Iterator Merge"]
H --> I["09 Cache Bloom"]
I --> J["10 Leveled Compaction"]
J --> K["11 Universal FIFO Stall"]
K --> L["12 Concurrent RateLimit"]
L --> M["13 ColumnFamily"]
M --> N["14 Transactions"]
N --> O["15 Checkpoint Ingest"]
O --> P["16 Embedded Production"]
P --> Q["17 Troubleshooting"]
Q --> R["18 Engine Selection"]
推荐阅读路径
| 路径 | 篇目顺序 | 适合谁 |
|---|---|---|
| 存储三角闭合 | 1 → 4 → 7 → 10 → 13 → 18 | 从 PG/列存转 LSM |
| 从 lsm-tree DIY 来 | 2 → 3 → 6 → 10 → 17 | 对照 toy vs 生产 |
| 从 Flink/stream 来 | 1 → 5 → 9 → 11 → 16 → 17 | 排查 state 慢 |
| Compaction 调优 | 10 → 11 → 12 → 17 | 平台 / DBA |
| 完整通读 | 1 → … → 18 | 系统掌握 |
各部分一句话价值
第一部分(01–03):生态定位、LevelDB 单线程骨架、RocksDB fork 后的 diff 地图——建立后续读源码的坐标系。
第二部分(04–06):WriteBatch
如何原子提交、MemTable 如何 flush 成 L0 SST、MANIFEST
如何描述 LSM 快照——写路径.disk 语义。
第三部分(07–09):Get 与
Snapshot 的 sequence 边界、Iterator 多路归并、tombstone
可见性、Block Cache 与 Bloom 如何裁剪读放大。
第四部分(10–12):Leveled
不变式、Universal/FIFO、write stall 状态机、并发 compaction
与 RateLimiter——与 storage/31
公式呼应的源码侧。
第五部分(13–15):Column Family 共享 WAL、事务 OCC/WritePrepared 边界、Checkpoint 与 External Ingest——嵌入与运维接口。
第六部分(16–18):Flink/TiKV/Kafka
Streams 对照、LOG/GetProperty 排查、RocksDB vs
InnoDB vs 列存选型地图。
六、六个关键问题(系列贯穿)
系列 index 归纳的六个问题,本篇先给出索引,后续篇章逐条钉源码:
- LevelDB 瓶颈在哪,RocksDB 改了什么? →
第 2–3 篇(单线程 vs
线程池、
WriteThread、Column Family)。 Get/Iterator如何从 MemTable 追到 Ln? → 第 4–9 篇(SuperVersion、MergeIterator)。- Compaction 如何驱动 write stall
与三种放大? → 第 10–12
篇(
CompactionPicker、StopWritesTrigger)。 - Column Family 与 WAL 如何共享又隔离? →
第 13 篇(
ColumnFamilyData)。 - Flink / TiKV 如何映射到 CF 与 key? → 第 16 篇(对读 stream/12)。
- 写 stall、空间膨胀、读放大如何诊断? → 第 17–18 篇(Wiki Troubleshooting + 选型)。
七、实验与复现入口
本系列性能相关实验统一放在
post/db/rocksdb/reproduce/,脚本包括
run_db_bench.sh、run_properties.sh。环境为
WSL2 上编译 RocksDB 9.x 的
db_bench;未在本篇执行的 benchmark
不写入正文结论。
第 4 篇起会给出 WAL 目录列表、flush 前后 SST
数量、rocksdb.stats 属性等可复现步骤。Flink
集群相关指标(checkpoint
耗时、状态体积)仅在读者自有环境验证,系列规划不伪造 Web UI
数字(见 PLAN.md 实验台账)。
八、小结
存储引擎三角中,RocksDB 占据 写优化 LSM 一角:前台顺序写、后台 compaction 换写吞吐,代价是读放大与写放大需要在生产里持续调参。它与 PostgreSQL B-Tree、列存 scan 不是替代关系,而是 负载分工——TiKV、Flink、Kafka Streams 把 RocksDB 嵌在各自分布式或流式栈的 本地持久化层。
本系列在 lsm-tree
DIY、storage/31–32、stream/12
已覆盖的内容之外,按 RocksDB 9.x 源码补全 写路径 →
读路径 → compaction → 嵌入 → 排查
的内核叙事。下一篇进入 LevelDB 1.23 基线:单线程 leveled
compaction 与 DBImpl::Write/Get
的最小骨架,为第 3 篇的 diff 地图提供对照尺。
参考资料
- O’Neil, Patrick E. et al., The Log-Structured Merge-Tree (LSM-Tree), Acta Informatica, 1996.
- Dayan, Niv & Idreos, Stratos, Dostoevsky: Better Space-Time Trade-offs for LSM-Tree Based Key-Value Stores, SIGMOD 2018(leveled vs tiering 分析框架)。
- Dayan, Niv & Idreos, Stratos, The Log-Structured Merge-Bush & the Wacky Continuum, SIGMOD 2017(tiering 与 merge bush 模型)。
- Data, R. et al., RUM Conjecture (SIGMOD 2016)(读/更新/内存权衡;与 WA/RA/SA 对照阅读)。
- Sears, Ryan & Ramanan, Rahul, bLSM: A General Purpose Log Structured Merge Tree, SIGMOD 2012(多核 compaction 与 stall 思路;工业对照)。
- Dean, Jeff & Ghemawat, Sanjay,
LevelDB(
google/leveldbREADME /doc/impl.md)。 - RocksDB Wiki, Architecture / Overview / RocksDB vs LevelDB。
- Boncz, Peter et al., MonetDB/X100: Hyper-Pipelining Query Execution, CIDR 2005(列存 scan 对照;见 columnar-engine)。
- Apache Flink / TiKV 官方文档(嵌入边界;A/B 级)。
- db-frontier/12 HTAP(HTAP 开放问题;B 级接口)。
- 本系列 index、stream/12、storage/31–32。
返回 系列目录 | 下一篇:LevelDB 基线:单线程 Leveled 与 DBImpl 骨架
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【RocksDB 内核机制】LevelDB · WAL · Compaction · Column Family · 生产嵌入
补全存储引擎三角最后一角:从 LevelDB 基线与 RocksDB 架构演进,到 WAL/MemTable/SST 写路径、Get/Iterator 读路径、Leveled/Universal compaction 与 write stall,再到 Column Family、事务、Checkpoint 与 Flink/TiKV 嵌入对照。
【RocksDB 内核机制】生产嵌入对照:Flink · TiKV · Kafka Streams
从 RocksDB 内核侧补全 Flink KeyGroup 前缀与增量 checkpoint SST 上传机制,对照 TiKV Region 引擎与 Kafka Streams changelog 容错;不重复 stream/12 作业侧全文,给出 Docker 复现步骤而不伪造 checkpoint 指标。
【RocksDB 内核机制】选型与存储栈阅读地图
用决策树收束 RocksDB 与 InnoDB、列存、湖仓的适用边界;给出存储引擎三角 + 数据平台全栈阅读地图,对接 query-engine/18 与 postgresql-kernel,并标注 HTAP/TiKV 续作入口。
数据库内核实验索引
汇总本站数据库内核文章:PostgreSQL / MySQL InnoDB / 列存引擎 / 数据湖与开放表格式 / 流式数据处理 / 分布式 OLAP 查询引擎 / RocksDB 内核,以及 LSM-Tree 从零实现实验。