这是我参与「第四届青训营 」笔记创作活动的第十三天
LSMT 存储引擎浅析
一、LSMT与存储引擎介绍
1.1 LSMT的历史
- LSMT是Log-Structured Merge-Tree的缩写,由Patrick 0 'Neil etc.在1996年的论文,The Log- Structured Merge-Tree (LSM-Tree), 提出。
- 相较而言,B-Tree出现就早得多了,在1970年由Bayer, R.; McCreight, E.提出。
- 早期的数据库系统一般都采用B-Tree家族作为索引,例如MySQL。2000年后诞生的数据库大多采用LSMT索引,例如Google BigTable, HBase, Canssandra等。
1.2 LSMT是什么
一言以蔽之,通过 Append-only Write + 择机 Compact 来维护索引树的结构。
1.3 存储引擎是什么?
传统数据库大致可以分为
- 计算层
- 存储层(存储引擎层)
介于二者之间还有一些界限比较模糊的组件,比如 Replication,MySQL 是用 bin log 独立于存储引擎,而对于一些 NoSQL 数据库(字节 Abase 1.0)来说,Replication 直接基于存储引擎的 WAL。
计算层主要负责 SQL 解析/ 查询优化 / 计划执行。我们重点关注存储层提供了什么能力。数据库著名的 ACID 特性,在 MySQL 中全部强依赖于存储引擎。
二、LSMT存储引擎的优势与实现
2.1 LSMT 与 B+Tree 的异同
经典 B+Tree 写入流程,
-
在 B+Tree 中,数据插入是原地更新的,装有 (10, 20, 30, 40) 的节点在插入和分裂后,原节点覆写成 (10, 15)。此外,B+Tree 在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡。
-
所以从高层次的数据结构角度来看,B+Tree 和 LSMT 并没有本质的不同,可以统一到一个模型里,根据 Workload 的不同互相转换。
-
尽管 LSMT 和 B+Tree 可以用一个模型描述,工程实践上我们还是用 LSMT 来表示一个 Append-only 和 Lazy Compact 的索引树,B+Tree 来表示一个 Inplace-Update 和 Instant Compact 的索引树。Append-only 和 Lazy Compact 这两个特性更符合现代计算机设备的特性。
2.2 为什么要采用 LSMT 模型?
All problems in computer science can be solved by another level of indirection ——From Butler Lampson
在计算机存储乃至整个工程界都在利用 Indirection 处理资源的不对称性,比方说内存价格上升了,就要想办法 offload 数据到别的廉价介质上。存储引擎面对的资源不对称性在不同时期是不同的。
HDD 时代:
顺序操作和随机操作的不对称性 ,由于不需要寻道,磁头始终能处在工作状态,基本都能做到至少 100MB/s 写吞吐,是点查的 25 倍!
SSD 时代:
顺序写和随机写的不对称性
无论对于 HDD 还是 SSD,顺序写都是一个很好的特质,LSMT 符合这一点,B+Tree 则依赖原地更新,会导致随机写。
2.3 LSMT 存储引擎的实现,以 RocksDB 为例
RocksDB 是一款十分流行的开源 LSMT 存储引擎,最早来自 Facebook(Meta),应用于 MyRocks,TiDB,在字节内部也有 Abase,ByteKV,ByteNDB,Bytable 等用户。
2.3.1 Write
为了确保操作的原子性,RocksDB 在真正执行修改之前会先将变更写入 WAL(Write Ahead Log),WAL 写成功则写入成功。因为即使这时候程序 crash,在重启阶段可以通过回放 WAL 来恢复或者继续之前的变更。操作只有成功和失败两种状态。
如果没有批量提交就只能链式唤醒了。
2.3.2 Snapshot & SuperVision
RocksDB 的数据由 3 部分组成,MemTable / ImmemTable / SST。直接持有这三部分数据并且提供快照功能的组件叫做 SuperVersion。
RocksDB 的 MemTable 和 SST 的释放与删除都依赖于引用计数,SuperVersion 不释放,对应的 MemTable 和 SST 就不会释放。对于读取操作来说,只要拿着这个 SuperVersion,从 MemTable 开始一级一级向下,就能查询到记录。那么拿着 SuperVersion 不释放,等于是拿到了快照。
2.3.3 Get & BloomFilter
由于 LSMT 是延迟 Compact 的且 SST 尺寸(MB 级别)比 B+Tree Node (KB 级别)大得多。所以相对而言,LSMT 点查需要访问的数据块更多。为了加速点查,一般 LSMT 引擎都会在 SST 中嵌入 BloomFilter,例如 RocksDB 默认的 BlockBasedTable。BloomFilter 可以 100% 断言一个元素不在集合内,但只能大概率判定一个元素在集合内。
RocksDB 的读取在大框架上和 B+ Tree 类似,就是层层向下。
2.3.4 Compact
Compact 在 LSMT 中是将 Key 区间有重叠或无效数据较多的 SST 进行合并,以此来加速读取或者回收空间。Compact 策略可以分为两大类。
- Level
三、 LSMT 模型理论分析
T: size ratio,每层 LSMT 比上一层大多少,L0 大小为 1,则 L1 大小为 T,L2 为 T^2,以此类推
L: level num,LSMT 层数
B: 每个最小的 IO 单位能装载多少条记录
M: 每个 BloomFilter 有多少 bits
N: 每个 BloomFilter 生成时用了多少条 Key
e−MNe^{- \frac{M}{N} } e−NM是 BloomFilter 的 false positive rate
S:区间查询的记录数量
3.1 LSMT模型算法复杂度分析——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 的原因是写入可以批量提交,但是读取的时候必须对齐到最小读取单元尺寸。
3.2 LSMT模型算法复杂度分析——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 策略增加了写放大,降低了读和空间放大。
四、LSMT存储引擎调优案例
TerarkDB aka LavaKV 是字节跳动内部基于 RocksDB 深度定制优化的自研 LSMT 存储引擎,其中完全自研的 KV 分离功能,上线后取得了巨大的收益。
4.1 Abase 图存储场景使用 TerarkDB
-
图存储场景描述
- Key size :20B ~ 30B
- Value size:数十 KB 级别
- 写多读少
-
收益结论:
延迟大幅度降低,长尾消失,扛住了比 RocksDB 高 50% 的负载。
4.2 Flink 流计算场景使用 TerarkDB
- 收益结论:
-
平均 CPU 开销在 3个作业上降低了 26%~39%
-
峰值 CPU 开销在广告作业上有明显的收益,降低了 67%
- live_feed_head 作业上峰值 CPU 开销降低 43%
- multi_trigger 受限于分配的CPU 资源,没有观察到峰值 CPU 收益( 平均 CPU 开销降低 39% )
-
平均容量开销在 3 个作业上降低了17%~31.2%
-
直播业务某集群容量不收缩,TerarkDB 的 schedule TTL GC 彻底解决了该问题
- 收益说明:
-
平均 CPU 收益主要来自于,开启 KV 分离,减少写放大
-
容量收益主要来自于 schedule TTL GC,该功能可以根据 SST 的过期时间主动发起Compaction,而不需要被动的跟随 LSM-tree 形态调整回收空间
4.3 Flink 使用 FIFO 和 TerarkDB 对比
线上 live head 作业,FIFO 和 TerarkDB 通过不同的方式减少了 Compaction ,相比于 Leveled Compaction, FIFO CPU 收益 29%, 略低于 TerarkDB 的 34% 。
FIFO 存在较严重的空间放大,live head作业上, FIFO 容量峰值 比 Leveled 大30%,而 TerarkDB 容量峰值比 Leveled 小 15%。