LSMT存储引擎浅析|青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第6天。
什么是LSMT以及存储引擎
- LSMT是Log-Structured Merge-Tree 的缩写,由Patrick O 'Neil etc.在1996年的论文《The Log-Structured Merge-Tree (LSM-Tree)》提出。
LSMT使用一种算法来延迟和批量处理索引更改,以一种类似于归并排序的高效方式将基于内存的组件的更改级联到一个或多个磁盘组件。在此过程中,所有索引值都可以通过内存组件或一个磁盘组件连续地进行检索(除了非常短的锁定周期外)。与传统的访问方法(如B- Tree)相比,该算法大大减少了磁盘臂的移动,并将在insert操作(使用传统访问方法进行插入操作时,磁盘臂的成本超过存储介质成本) 提高性能成本。LSM-tree方法还可以推广到插入和删除以外的操作。
一言以蔽之,通过Append-only Write +择机 Compact来维护结构的索引树。
- 数据库中大致可以分层为 计算层 和存储层(存储引擎)。
- 保障事务ACID特性
- 屏蔽IO 细节提供更好的抽象
- 提供统计信息与Predicate Push Down能力
- 存储引擎不掌控IO细节,让操作系统接管
LSMT存储引擎的优势
LSMT与B+Tree 异同点
| 索引结构 | 树不平衡时 | 数据插入 |
|---|---|---|
| LSM-Tree | 当树不平衡或者垃圾过多时,有专门 Compact 线程进行 Compact,可以称之为延迟(Lazy)的。 | 数据的插入是追加的(Append-only) |
| B+Tree | B+Tree 在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡。 | 原地更新 |
-
思考一个问题,B+Tree 能不能把部分数据采用追加写,然后让后台线程去 Compact 维护树结构呢?或者 LSMT 能不能只有一层 L0,ImmemTable 给 Flush 线程之后,立马 Compact 呢?
-
答案是都可以。前者的做法叫做 Fractal tree(分型树)应用在了 TokuDB 中。后者的做法在 OceanBase 或者类似对延迟有严格要求的在线数据库中得到了应用,因为 LSMT 层数越少,读取越快。
所以从高层次的数据结构角度来看,B+Tree 和 LSMT 并没有本质的不同,可以统一到一个模型里。
LSM-tree优势
-
相对于 B+Tree 的优势
- 顺序写模型对于 SSD 设备更友好
- SST 不可修改的特性使得其能使用更加紧凑的数据排列和加上压缩
- 后台延迟 Compact 能更好利用 CPU 多核处理能力,降低前台请求延迟
-
相对于 HashTable 的优势
- LSMT 存储引擎是有序索引抽象,HashTable 是无序索引抽象。无序索引是有序索引的真子集。
- LSMT 相比于 HashTable 更加通用。HashTable 能处理点查请求,LSMT 也能,但 LSMT 能处理 TopK 请求,但 HashTable 就不行了。为了避免维护多套存储引擎,绝大多数数据库都直接采用一套有序的存储引擎而非针对点查和顺序读取分别维护两个引擎。
LSMT模型算法复杂度分析
T: size ratio,每层 LSMT 比上一层大多少,L0 大小为 1,则 L1 大小为 T,L2 为 T^2,以此类推
L: level num,LSMT 层数
B: 每个最小的 IO 单位能装载多少条记录
M: 每个 BloomFilter 有多少 bits
N: 每个 BloomFilter 生成时用了多少条 Key
是 BloomFilter 的 false positive rate
S:区间查询的记录数量
Ref: LSM-based Storage Techniques: A Survey Short Range Query / Long Range Query / Space Amplification。
- Level
Write:每条记录抵达最底层需要经过 L 次 Compact,每次 Compact Ln 的一个小 SST 和 Ln+1 的一个大 SST。设小 SST 的大小为 1,那么大 SST 的大小则为 T,合并开销是 1+T,换言之将 1 单位的 Ln 的 SST 推到 Ln+1 要耗费 1+T 的 IO,单次 Compact 写放大为 T。每条记录的写入成本为 1/B 次最小单位 IO。三者相乘即得结果。
Point Lookup:对于每条 Key,最多有 L 个重叠的区间,每个区间都有 BloomFilter,失效率为e−MNe^{- \frac{M}{N} } e−NM,只有当 BloomFilter 失效时才会访问下一层。因此二者相乘可得读取的开销。注意,这里不乘 1/B 的原因是写入可以批量提交,但是读取的时候必须对齐到最小读取单元尺寸。
- Tier
Write:每条记录抵达最底层前同样要经过 L 次 Compact,每次 Compact Ln 中 T 个相同尺寸的 SST 放到 Ln+1。设 SST 大小为 1,那么 T 个 SST Compact 的合并开销是 T,换言之将 T 单位的 Ln 的 SST 推到 Ln+1 要耗费 T 的 IO,单次 Compact 的写放大为 T / T = 1。每条记录的写入成本为 1/B 次最小单位 IO。三者相乘即得结果。
Point Lookup:对于每条 Key,有 L 层,每层最多有 T 个重叠区间的 SST,对于整个 SST 来说有 T *
L 个可能命中的 SST,乘上 BloomFilter 的失效率即可得结果。
总结,Tier 策略降低了写放大,增加了读放大和空间放大,Level 策略增加了写放大,降低了读和空间放大。