技术解读
在 Rust 里重建一颗 LSM-Tree:skyzh/mini-lsm 深度拆解
项目背景与痛点
LSM-Tree 是当今写密集型存储的事实标准:RocksDB、LevelDB、TiKV、Cassandra、ScyllaDB,甚至 ClickHouse 的 MergeTree 家族,骨子里都是同一套思路——把随机写变成顺序写,用后台合并换取写入吞吐。
问题在于,理解它很难。论文只讲清了「为什么」,而从「为什么」到「能跑起来的代码」,中间隔着 WAL 的 fsync 语义、Block 的前缀压缩、Manifest 的原子切换、Compaction 的层级调度。直接读 RocksDB 源码?几十万行代码、线程池、内存分配器、版本管理(VersionEdit / SuperVersion)会迅速淹没你。读 MIT 6.824 的 SimpleDB 或者各种「200 行实现一个 KV」的博客?又太浅,浅到根本不涉及崩溃恢复和 MVCC——而这恰恰是工程实现中最容易出错的部分。
mini-lsm 的价值就在于它卡在中间的生态位:它不是一份可用的数据库,而是一条可执行的、有测试兜底的学习路径。作者 skyzh 把 LevelDB/RocksDB 的核心机制拆成三周、每天一个主题,配合完整的测试套件,让一个人真的能在两三周内亲手写出一颗能通过崩溃一致性测试的 LSM 引擎。
核心技术揭秘
整个引擎的分层非常克制,几乎每个模块都能对应到工业实现中的同名组件。
第一层:写路径与崩溃恢复。 每次 put 先追加 WAL,再写内存中的 MemTable(可选 crossbeam-skiplist 或 BTreeMap)。MemTable 超过阈值就冻结、刷成 L0 的 SST 文件,并在 Manifest 里记一笔。这里有一个容易被忽略的设计:Manifest 用 JSON 记录 SST 列表与层级状态,本身不做增量日志,而是整体覆盖写——教学场景下足够,但也恰好暴露了真实引擎为什么要用 VersionEdit + 原子 rename。
第二层:Key 编码与 MVCC。 mini-lsm 的第三周是精华。用户 key 与 u64 时间戳拼接,时间戳按降序编码(u64::MAX - ts 大端序),这样同一个 key 的所有版本在 SST 里天然按「新→旧」排列,迭代器扫到的第一个可见版本就是答案。删除被表达为墓碑记录,配合 watermark 判定快照可见性,一套机制同时解决点查、范围扫描和多版本读。这个技巧在 TiKV 和 RocksDB 里叫「user key + seq」,mini-lsm 把它讲透了。
第三层:迭代器的组合式抽象。 这是全项目最漂亮的部分。MergeIterator 用二叉堆做多路归并,TwoMergeIterator 把「MemTable 流」和「层内流」串成一条有序流,再套一层过滤可见性的 LsmIterator。所有迭代器统一实现 DoubleEndedIterator,于是 scan 天然支持正向与反向遍历,seek_to_key 也只是在每层做一次定位。Rust 的 trait object 加泛型在这里不是炫技,而是让「加一层新语义」的成本降到几十行。
第四层:SST 的细节工程。 Block 内部对相邻 key 做前缀压缩,每 16 个 key 设一个 restart point,查找时先二分 restart points 再线性扫;块尾附 CRC32 校验。点查走 Bloom Filter 直接跳过整个 SST,范围扫描则依赖 block cache 减少解码开销。这些细节决定了引擎是「玩具」还是「像样」。
功能亮点与差异
相比同类的教学项目,mini-lsm 有三处明显不同。
一是测试驱动到近乎偏执。 每个 day 的骨架代码旁边都摆着对应的集成测试,从 MVCC 可见性到 compaction 后的数据完整性,全部可验证。你不是「写完祈祷它能跑」,而是「看着红变绿」。
二是故障注入模拟器。 仓库提供了可对文件系统注入截断、半写、断电等故障的模拟层,用来验证 WAL 重放与 Manifest 恢复的正确性。绝大多数「手写 KV」教程完全没有这一环,而这恰恰是存储引擎最贵的部分。
三是策略可选。 第二周同时实现 Simple Leveled、Tiered(Universal)和基于 size ratio 的 Leveled Compaction,能直接对比写放大与读放大的权衡——这种「一次实现三种策略」的收益,在真实项目里几乎不可能获得。
应用场景与落地建议
不要用于生产。 缺后台线程调度、Compaction 限速、WAL 复用与周期性清理、SST 生命周期管理,错误处理也偏教学化。Arc<RwLock<State>> 的粗粒度锁在高并发下会迅速成为瓶颈。
适合三种用途: 团队内部的存储内核培训;为嵌入式 KV 或本地索引做原型验证;把你自己的 compaction 策略丢进它的模拟器里做对照实验。
真要基于它改造,优先级排序是:先把 WAL 的 fsync 与批量提交做对,再把 compaction 挪到独立线程并加限速,然后处理读路径与 compaction 的锁竞争,最后补上块级索引和更细的缓存淘汰策略。
综合评价
优点很清楚:路径设计有教学法,模块边界干净,Rust 的迭代器抽象把 LSM 的多路归并表达得极为优雅,模拟器是同类项目中罕见的加分项。它让你写完的不是「能存 key 的玩具」,而是「能被测试证明正确的引擎」。
短板同样明显:这是教学实现,性能调优、并发细节、运维能力全部留白;部分 day 的骨架需要你自己补全,MVCC 与 compaction 两章对没接触过存储系统的读者是陡坡。
一句话总结:如果你想真正搞懂 LSM-Tree 里那些「论文不会写、源码读不动」的部分,mini-lsm 目前是中文圈之外最好的一条路。
评论