LSM 树:Compaction 与写放大

kp-021 · 存储引擎与日志进阶约 30 分钟已校对

前置知识点

前置:

相关知识点

相关:

学习进度:

一句话定义

LSM 树(Log-Structured Merge-Tree)把写入变成「内存表顺序追加 + 后台归并排序(Compaction)」:用顺序 IO 承接随机写,代价是读需要跨多层查找、后台 Compaction 产生读写放大。

为什么重要

前置知识

kp-019(页与顺序/随机 IO 的代价差)。

核心概念

原理与机制

写入路径全部是顺序 IO:追加 WAL → 更新 MemTable → 满则整体刷成 SSTable → 后台线程按「大小分层(Tiered)或逐层(Leveled)」策略归并。读路径:先查 MemTable,再按层级查 SSTable,Bloom Filter 排除无关文件,最后命中最新版本(同键多版本按时间戳取新)。Leveled Compaction 把每层与下一层重叠部分归并,读放大小、空间放大小,但单次 Compaction 大;Tiered 归并多个满层文件,写放大小、读放大与空间放大较大。量化三角:Leveled 写放大 ≈ 每层数 × 层数(典型 10 层 × 10 = 10~30 倍),即一条 1KB 数据最终要写 10~30KB;RocksDB 因此提供 Compaction 风格调优与列族隔离热点。读放大来自跨层查找,Bloom Filter(假阳率 ~1%)把它压回常数级。

实例或案例

监控指标入库(每秒百万点、按时间序追加):选 RocksDB 系(TiKV/InfluxDB IOx 等)看中的正是顺序写吞吐;查询「某指标近 1 小时序列」时按时间前缀在底层文件顺序扫,表现优异。反过来,若负载是「随机更新同一批用户行的余额」,LSM 的 Compaction 会反复重写包含这些键的文件,写放大击穿磁盘预算,B+ 树引擎(原地更新 + Undo)更合适。压测时观测三个指标:write_amplification = 实际写入字节 / 业务写入字节、P99 读延迟随 Compaction 积压的抖动、磁盘空间占用与逻辑数据的比值。

常见误区

自测题

  1. 为什么 LSM 需要 Bloom Filter? 答:读可能命中任意层任意 SSTable,逐文件二分代价高;Bloom 以极小空间给出「肯定不在」的判断,直接排除绝大多数文件,控制读放大。
  2. Leveled 与 Tiered Compaction 的核心取舍? 答:Leveled 层内不重叠,读与空间表现好但写放大约等于层数量级;Tiered 归并更粗,写放大小但读放大与空间放大更大。

公式或模型

写放大(Leveled,放大因子 r、层 n):WAF ≈ r × n(r=10、n=4~5 时 10~30 倍)。空间放大上界:Leveled ≈ 1 + 1/r;Tiered ≈ r。读放大(含 Bloom):RAF ≈ n × P_false + log(每文件)。

图示

MemTable(内存) L0 文件 a L0 文件 b L1(归并产物) L1 旧文件(被取代) Compaction:跨层归并 清理旧值与墓碑
LSM:内存表 → 冻结为 L0 SSTable → 逐层 Compaction 归并,旧文件被新产物取代

直观类比

B+ 树是「随到随上架」的图书馆:每本书立即插入正确格位(随机写),取书一步到位;LSM 是「先堆周转箱、夜里统一归架」的仓库:进货(写)极快,取货要翻几个周转箱,管理员(Compaction)深夜把箱子归并上架,代价是搬运翻倍(写放大)。

与其他知识点的关系

kp-009 是对照的读优化结构;kp-020 的 WAL 同样是它的写入保险;kp-027 的宽列与 KV 家族几乎全部建构在 LSM 之上;kp-012 的执行计划在 LSM 引擎中读数含义不同(SSTable 扫描)。

延伸阅读