专栏文章
专栏文章
存储算法系列
1. 存储算法 #01:LSM Tree 2. 存储算法 #02:Bloom Filter 3. 存储算法 #03:B+ Tree 4. 存储算法 #04:SSTable 5. 存储算法 #05:Copy-on-Write(COW) 6. 存储算法 #06:Roaring Bitmap 7. 存储算法 #07:WAL(Write-Ahead Log) 8. 存储算法 #08:Bitcask 9. 存储算法 #09:Redo 与 Undo Log 10. 存储算法 #10:Varint 与 ZigZag 编码 11. 存储算法 #11:Delta 编码 12. 存储算法 #12:LZ4 与 Snappy 与 Zstd

存储算法 #01:LSM Tree

发布于 2026-06-02 02:05 👁 16 次阅读
#算法#storage#lsm-tree#rocksdb#write-optimization

LSM Tree(Log-Structured Merge Tree)是现代高写入吞吐存储引擎的核心数据结构,通过将随机写转换为顺序写,大幅提升写入性能。RocksDB、LevelDB、TiKV、Cassandra、HBase 均以 LSM Tree 为底层引擎。

相关文章Bloom Filter · B+ Tree · SSTable


目录

章节 说明
核心思想 随机写 → 顺序写的转换原理
整体结构 MemTable / SSTable / 多层 Level
写入路径 WAL → MemTable → Flush → Compaction
读取路径 多层查找与 Bloom Filter 优化
Compaction 策略 Size-Tiered vs Leveled
三种放大 读放大、写放大、空间放大
异常场景 崩溃恢复、读性能退化、Compaction 积压
与 B+ Tree 对比 适用场景的根本差异

核心思想

问题:磁盘随机写极慢(HDD ~100 IOPS,SSD ~10万 IOPS),而顺序写快 10~100 倍。

B+ Tree 的写入问题:
  更新 key=X → 找到对应磁盘页 → 原地修改 → 写回
  → 随机 I/O,受限于磁头寻道时间

LSM Tree 的解法:
  所有写入 → 追加到内存缓冲区(MemTable)
  内存满了 → 顺序写一个不可变文件(SSTable)到磁盘
  → 全部是顺序写,磁盘友好

代价:写入变快,读取变慢(数据分散在多个文件),需要 Compaction 控制文件数量。


整体结构

lsm tree structure

内存层:
  MemTable(可写)    ← 当前接受写入的内存表(红黑树/跳表,有序)
  Immutable MemTable  ← MemTable 满后转为不可变,等待 Flush

磁盘层(多级 SSTable):
  L0:Flush 直接写入,文件间 key range 可以重叠(最新),通常 4~8 个文件
  L1:约 10MB,文件间 key range 不重叠
  L2:约 100MB,文件间 key range 不重叠
  L3:约 1GB
  ...(每层大小 = 上层 × 放大因子,通常 10)

WAL(Write-Ahead Log):
  在 MemTable 写入前先追加到 WAL
  崩溃恢复时用 WAL 重建 MemTable

写入路径

Client 写入 key=X, value=V:

① 追加到 WAL(顺序写磁盘,持久化保证)
② 写入 MemTable(内存红黑树/跳表,有序插入,O(log N))
③ 返回客户端成功  ← 写入完成,极快

后台流程:
④ MemTable 达到阈值(如 64MB)
   → 转为 Immutable MemTable,创建新 MemTable
⑤ 后台线程将 Immutable MemTable Flush 到磁盘 L0(顺序写一个 SSTable 文件)
   → 删除对应的 WAL 段
⑥ L0 文件数超过阈值 → 触发 Compaction
   → 合并 L0 + L1 文件,生成新的 L1 SSTable
   → 依此类推,逐级向下 Compaction

写入性能关键:步骤①②完成即可返回,③④⑤⑥全在后台异步执行,不阻塞写入。


读取路径

Client 读取 key=X:

① 查 MemTable(内存,O(log N))→ 找到则返回
② 查 Immutable MemTable(可能有多个)
③ 从 L0 最新文件开始逐个查找(L0 文件间可能重叠,需全查)
④ 查 L1 → 二分定位包含 key 的文件 → 读取
⑤ 查 L2 → ... → Ln

最坏情况:需要查每一层,读放大严重

优化:每个 SSTable 配备 Bloom Filter
  if BloomFilter(key) == 可能存在:
    实际读取 SSTable 文件
  else:
    直接跳过这个文件(减少 90%+ 的无效读取)

读取性能关键:Bloom Filter 让大多数"不存在的 key"查询只需内存操作,无需磁盘 I/O。


Compaction 策略

Size-Tiered(大小分层,Cassandra 默认)

思想:相同大小的 SSTable 积累到 N 个后合并成一个更大的文件

L0: [1MB][1MB][1MB][1MB] → 合并 → [4MB]
L1: [4MB][4MB][4MB][4MB] → 合并 → [16MB]
L2: [16MB][16MB]...

优点:写放大低(每次 Compaction 合并的文件少)
缺点:
  空间放大高(同一 key 的多个版本分散在不同层)
  读放大高(同一层的多个文件都可能包含目标 key)
适合:写多读少,时序数据

Leveled(分层压缩,LevelDB/RocksDB 默认)

思想:每层内 SSTable 的 key range 不重叠,层间大小有固定比例

L1: [a-m][n-z]            (文件间无重叠)
L2: [a-d][e-h][i-l][m-p][q-t][u-z]

Compaction 时:
  选 L1 的一个文件 [a-m]
  找 L2 中与其 key range 重叠的所有文件
  合并 → 写回 L2(保持无重叠)

优点:
  读放大低(每层最多一个文件包含目标 key)
  空间放大低(旧版本快速被清理)
缺点:写放大高(一个文件可能触发多层 Compaction)
适合:读写均衡,通用场景(RocksDB/TiKV 默认)

三种放大

存储引擎设计的核心三角矛盾,无法同时最小化:

放大类型 定义 LSM Tree B+ Tree
写放大(WA) 实际写磁盘量 / 用户写入量 中~高(Compaction 反复写) 低(原地修改)
读放大(RA) 实际读磁盘量 / 用户读取量 中(多层查找) 低(树高固定 3~4)
空间放大(SA) 磁盘占用 / 实际数据量 中(旧版本未及时清理) 低(原地更新)
RocksDB Leveled Compaction 典型数值:
  写放大:10~30×(每条数据平均被重写 10~30 次)
  读放大:< 10×(Bloom Filter 后接近 1×)
  空间放大:1.1~1.3×

异常场景

崩溃恢复

场景:MemTable 有数据,Flush 前进程崩溃

恢复流程:
  重启 → 读取 WAL → 重放 WAL 中的写操作 → 重建 MemTable
  → 继续正常服务

WAL 必须在 MemTable 写入前完成(先写 WAL 再写内存)
否则崩溃后数据无法恢复

读性能退化(L0 文件堆积)

场景:写入速度 > Compaction 速度,L0 文件越来越多

影响:
  L0 文件间 key range 重叠,每次读需扫描所有 L0 文件
  读放大急剧增大,延迟飙升

解决:
  L0 文件数超过软限制(如 20)→ 限速写入(stall writes)
  L0 文件数超过硬限制(如 36)→ 停止写入(stop writes)
  → 等 Compaction 追上后恢复

RocksDB 配置:
  level0_slowdown_writes_trigger = 20
  level0_stop_writes_trigger = 36

Compaction 写入风暴

场景:大量数据写入后触发多层 Compaction,磁盘 I/O 打满

影响:
  Compaction 和用户读写竞争磁盘带宽
  读写延迟 P99 飙升

缓解:
  限制 Compaction 的 I/O 带宽(RocksDB rate_limiter)
  使用独立磁盘做 Compaction I/O
  调大 L1 大小,减少 Compaction 频率

与 B+ Tree 对比

维度 LSM Tree B+ Tree
写入 ✅ 极快(顺序写) ⚠️ 慢(随机写)
点查 ⚠️ 较慢(多层查找) ✅ 快(树高固定)
范围查询 ⚠️ 需要合并多层迭代器 ✅ 叶节点链表,天然有序
空间 ⚠️ 旧版本延迟清理 ✅ 原地更新
崩溃恢复 WAL 重放 WAL + redo log
典型系统 RocksDB、Cassandra、TiKV MySQL InnoDB、PostgreSQL

选型原则


参考资料

  • O'Neil, P. et al. (1996). The Log-Structured Merge-Tree
  • RocksDB Wiki: https://github.com/facebook/rocksdb/wiki
  • 《Designing Data-Intensive Applications》Ch.3 — Storage and Retrieval
← 返回列表

评论 (0)

暂无评论,来留下第一条吧。

发表评论