这个系列用 5 篇文章,从零实现一个可运行的 LSM-Tree KV 存储引擎。不是照搬 LevelDB 的代码,而是从设计决策出发,搞清楚每个组件为什么长这样,然后亲手写一遍。最后用 Rust 重写核心模块,用数据说话。
适合谁读:对数据库内核、存储引擎、系统编程感兴趣的工程师。假设你熟悉 C 语言和基本数据结构,不假设数据库内核背景。
系列目录
第 1 篇:LSM-Tree 全景:为什么要先写日志再排序
从一个问题出发:B-Tree 的随机写太慢,有没有办法把随机写变成顺序写?
- B-Tree vs LSM-Tree 的本质差异
- 写放大 / 读放大 / 空间放大的数学推导——不是背公式,是自己推一遍
- WAL、MemTable、SSTable、Compaction、Bloom Filter 各组件的角色与协作
- 完整的读写路径 walkthrough
第 2 篇:WAL + MemTable:崩溃了也不丢数据
内存很快但断电就没了,磁盘持久但随机写太慢。WAL 和 MemTable 解决这个矛盾。
- WAL 的 32KB Block 对齐 + record 格式设计(附完整
wal.c/h) - 跳表(Skip List)的概率平衡 + O(log n) 插入/查找(附完整
skiplist.c/h) - InternalKey
编码:
[user_key | sequence(7B) | type(1B)] - 崩溃恢复的正确性证明
第 3 篇:SSTable + Bloom Filter:磁盘上的有序表
MemTable 写满后要 flush 到磁盘。怎么存才能既省空间又查得快?
- Data Block 前缀压缩 + restart point 二分查找
- Bloom Filter 双重哈希:用 10 bits/key 把误判率压到 ~1%
- SSTable 完整布局:Data Block → Filter Block → Index Block → Footer
- Builder 和 Reader 的完整 C 实现
第 4 篇:Compaction:LSM-Tree 的心脏手术
SSTable 只增不删,读放大和空间放大会持续恶化。Compaction 是 LSM-Tree 活下去的关键。
- 最小堆多路归并迭代器——同时遍历多个有序源
- Level 分层结构 + L0 → L1 Compaction 选择策略
- Tombstone 下推:删除标记什么时候才能安全丢弃?
- Version / VersionEdit / MANIFEST 版本管理
- Leveled vs Size-Tiered vs Universal 三种策略量化对比
第 5 篇:完整引擎 + Rust 重写对比
把所有组件组装成完整引擎,然后用 Rust 重写,用 benchmark 说话。
- 完整 DB
API:
Open / Put / Get / Delete / Iterator / Snapshot - 单写多读并发控制 + 崩溃恢复流程
- Rust 重写:5 个”编译器不让我过”的真实故事
- C / Rust / LevelDB 三方 benchmark 对比,拆解架构选择对性能的影响
延伸阅读
如果你读完这个系列还想继续深挖:
- LevelDB LRU Cache 实现分析——Block Cache 和 Table Cache 的实际实现
- LevelDB 使用与参数调优——从用户视角理解引擎行为