LSM Tree
LSM Tree(Log-Structured Merge Tree,日志结构合并树)是一种面向高并发写入优化,同时兼顾查询效率的键值存储模型。
一、核心主线
LSM Tree被广泛应用于 BigTable、HBase、Cassandra、TiDB 等 NoSQL 或分布式数据库中。
核心思路是:
将随机写转换为内存写和磁盘顺序写,以充分发挥内存与磁盘的性能优势。
整体数据流转过程:
1 | 写请求 |
二、LSM Tree 的设计基础
1. 顺序读写优于随机读写
LSM Tree 建立在一个重要结论之上:
磁盘或内存的顺序读写性能远高于随机读写性能。
这一规律不仅适用于机械硬盘,也适用于 SSD。
- 顺序读写:按照文件中数据的排列顺序进行操作,例如向文件尾部追加数据;
- 随机读写:不遵循文件中数据的排列顺序,需要在不同位置间跳转。
传统原地更新会造成大量随机写,而 LSM Tree 尽量不在磁盘中直接修改旧数据,而是:
- 先在内存中处理写入;
- 再将有序数据批量刷入磁盘;
- 后台通过合并操作清理旧数据。
这样便把大量随机写转换成了顺序写。
三、LSM Tree 的核心组成
1. WAL
WAL 全称为 Write-Ahead Log,即预写日志。
MemTable 位于内存中,如果机器宕机或进程异常退出,尚未持久化的数据就会丢失。因此,修改 MemTable 前需要先把数据变更追加到磁盘上的 WAL 文件。
1 | 写请求 |
WAL 的核心作用是:
- 为内存数据提供持久化保障;
- 系统异常后可以根据日志恢复数据;
- 在保证可靠性的同时,保留内存写入的高性能。
2. MemTable
MemTable 是位于内存中的有序数据结构,用于保存最近更新的数据。
它按照 Key 的字典序组织数据。LSM Tree 并不限制其具体实现,只要满足以下要求即可:
- 数据保持有序;
- 写入效率高;
- 查询效率较高。
常见实现包括:
- 红黑树;
- 跳表。
2.1 新增数据
直接将新的 Key 和 Value 插入 MemTable。
2.2 修改数据
- 如果 Key 已存在于 MemTable,直接修改;
- 如果不存在,则在 MemTable 中插入该 Key 的新值。
LSM Tree 不需要立即找到并修改磁盘中的旧数据。
2.3 删除数据
LSM Tree 通常不会立即物理删除数据,而是写入一条带有 tombstone(墓碑)标记的记录。
1 | 删除 Key |
删除操作因此被转换为一次普通写入,避免了磁盘随机删除。
3. Immutable MemTable
当 MemTable 的数据量达到容量阈值,例如 32 MB 时,它会转变为只读的 Immutable MemTable。
随后系统会:
- 将当前 MemTable 冻结为 Immutable MemTable;
- 创建一个新的空 MemTable;
- 新 MemTable 继续接收写请求;
- 后台线程把 Immutable MemTable 持久化到磁盘。
1 | 旧 MemTable 达到阈值 |
这样可以避免数据持久化过程阻塞新的写请求。
4. SSTable
SSTable 全称为 Sorted String Table,是 LSM Tree 在磁盘上的主要持久化结构。
它具有三个重要特征:
- 持久化:数据保存在磁盘中;
- 有序:记录按照 Key 的字典序排列;
- 不可变:文件生成后不再原地修改。
SSTable 中的 Key 和 Value 连续存储,文件还会保存 Key 对应数据位置的偏移量,形成稀疏索引,以提高查询速度。
1 | SSTable |
四、数据冗余与 Compaction
1. 为什么需要合并
MemTable 会不断被持久化为新的 SSTable,因此同一个 Key 可能同时存在于多个 SSTable 中。
例如:
1 | 旧 SSTable:user:1 = A |
其中新 SSTable 中的 B 才是最新值,旧值 A 已经失效,但仍然占用磁盘空间。
删除操作也类似:旧 SSTable 中可能保存数据,新 SSTable 中保存其 tombstone 标记。
为了清理冗余版本和已删除数据,LSM Tree 会周期性执行 Compaction(合并)。
2. Level 分层
LSM Tree 使用 Level 对 SSTable 进行分层:
1 | Immutable MemTable |
- Immutable MemTable 刷盘后形成 Level 0 SSTable;
- Level N 中的数据通过合并下沉到 Level N+1;
- 层级越低,数据通常越新;
- 层级越高,数据通常越旧。
如果同一个 Key 同时存在于多个层级,应优先使用较低层级中的值。
3. Leveled Compaction Strategy
主要合并策略是 LCS(Leveled Compaction Strategy,分层合并策略)。
其基本规则包括:
- SSTable 文件大小通常保持一致,例如默认约为 160 MB;
- 每个 Level 都有总容量限制;
- 层级越高,允许保存的数据总量越大;
- 单个 SSTable 内部的 Key 有序;
- Level 1~N 中,同一层的不同 SSTable 之间也按照 Key 范围有序且互不重叠。
容量示例:
1 | Level 1:总容量约 10 GB |
4. 分层合并过程
当 Level N 的文件总量超过阈值时:
- 从 Level N 选择一个 SSTable;
- 找出 Level N+1 中与其 Key 范围有交集的 SSTable;
- 将这些 SSTable 合并;
- 清除重复记录和已失效的旧版本;
- 生成若干新的 SSTable;
- 将新文件归入 Level N+1;
- 如果 Level N+1 也超出容量限制,则继续向下合并。
1 | Level N 的 SSTable |
5. Level 0 的特殊性
Level 0 SSTable 直接来自不同的 Immutable MemTable,因此多个 Level 0 文件的 Key 范围可能相互重叠。
经过合并后:
- Level 1~N 同一层的 SSTable 之间通常不存在 Key 范围重叠;
- 一个 Key 在同一个非 Level 0 层级中至多出现一次;
- 查询高层级数据时更容易定位目标 SSTable。
五、写数据流程
1. 完整写入链路
LSM Tree 处理写请求的过程如下:
- 将数据变更追加到 WAL;
- 把数据写入 MemTable;
- 此时即可向客户端返回成功;
- MemTable 达到容量阈值后转为 Immutable MemTable;
- 新建一个 MemTable 继续处理请求;
- 后台将 Immutable MemTable 持久化为 Level 0 SSTable;
- Level 0 超过容量阈值后触发 Compaction;
- SSTable 逐层下沉至 Level 1、Level 2,直到容量满足限制。
1 | 客户端写请求 |
2. 写性能高的原因
LSM Tree 写性能较高,主要因为:
- 首先写入内存中的 MemTable;
- WAL 使用追加写,属于顺序写;
- 不直接在磁盘中随机查找并修改旧记录;
- 数据以批量、有序的方式刷入磁盘;
- 数据整理和旧版本清理由后台 Compaction 完成。
六、读数据流程
1. 查询顺序
为了读到某个 Key 的最新值,LSM Tree 会按照数据从新到旧的顺序查找:
1 | MemTable |
具体流程:
- 先查询 MemTable;
- 未找到时查询 Immutable MemTable;
- 继续查询 Level 0 中最新的 SSTable;
- 遍历 Level 0 的相关 SSTable;
- 再依次查询 Level 1~N;
- 找到后立即返回,全部未找到则表示数据不存在。
2. 为什么必须从新到旧查询
同一个 Key 可能存在多个版本:
1 | MemTable:最新版本 |
如果不按新旧顺序查询,就可能返回已经失效的旧值。
同样,如果先找到 tombstone,就应认为该数据已被删除,不能继续返回更旧层级中的历史值。
七、LSM Tree 的核心权衡
1. 优势
- 将随机写转换为内存写和磁盘顺序写;
- 非常适合高并发写入;
- WAL 保证内存数据的可靠性;
- SSTable 有序并带有索引,可以兼顾查询效率;
- 数据可以通过分层和合并持续整理;
- 很适合作为分布式 NoSQL 数据库的底层存储模型。
2. 代价
- 同一个 Key 可能存在多个版本,占用额外空间;
- 读取数据可能需要查询多个内存结构和 SSTable;
- 后台 Compaction 会消耗 CPU、磁盘 I/O 和存储带宽;
- 数据不会在修改或删除时立即原地更新,而是延迟到合并阶段清理。
LSM Tree 的本质权衡是:
通过增加读路径和后台合并的复杂度,换取更高的写入性能。
八、核心架构思想
- 顺序读写性能高于随机读写,是 LSM Tree 成立的基础。
- 写请求先进入 WAL,再写入 MemTable,在性能和可靠性之间取得平衡。
- MemTable 保存最新数据,并通过有序结构提高读写效率。
- 删除不立即清除旧数据,而是写入 tombstone,将随机删除转换为普通写入。
- Immutable MemTable 让后台刷盘不阻塞新的写请求。
- SSTable 是持久化、有序且不可变的磁盘文件。
- 同一 Key 可以存在多个版本,较新的结构或较低层级中的值优先。
- Compaction 负责清除冗余版本、整理数据并回收空间。
- Level 0 的文件可能重叠,Level 1~N 同层文件通常不重叠。
- 写入路径短而快速,读取路径需要从新到旧逐层查找。
- LSM Tree 用读放大、写放大和后台合并成本,换取高并发写入能力。
九、一句话总结
LSM Tree 通过 WAL 保证可靠性,在 MemTable 中承接高速内存写入,再将数据有序地刷入不可变的 SSTable,并通过分层 Compaction 清理旧版本和 tombstone;它用更复杂的读取路径与后台合并成本,将大量随机写转换为顺序写,因此非常适合高并发写入的 NoSQL 和分布式数据库。