这是我参与「第四届青训营 」笔记创作活动的的第14天
今天是大数据专场基础班的第十四次课,主要内容是LSMT 存储引擎浅析,主要分为下面四个板块。
一、 LSMT与存储引擎介绍
1. LSMT的历史
- LSMT是 Log-Structured Merge-Tree 的缩写,由Patrick O '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等
2. LSMT是什么?
- 一言以蔽之,通过Append-only Write +择机Compact来维护结构的索引树
3. 存储引擎是什么?
-
以单机数据库MySQL为例,大致可以分为:
- 计算层
- 存储层(存储引擎层)
-
计算层主要负责SQL解析/查询优化/计划执行
-
数据库著名的ACID特性,在MySQL中全部强依赖于存储引擎
3.1 ACID是什么/存储引擎哪些组件保障了这些特性?
-
Atomicity
-
Write-Ahead Log(WAL) / Redo Log
-
Consistency(Correctness)依赖于数据库整体
-
lsolation
-
Snapshot /2PL(Phase Lock)
-
Durability
-
Flusher遵循Sync语意
-
除了保障ACID以外,存储引擎还要负责:
- 屏蔽IO 细节提供更好的抽象
- 提供统计信息与Predicate Push Down能力
-
存储引擎不掌控IO细节,让操作系统接管,例如使用mmap,会有如下问题:
- 落盘时机不确定造成的事务不安全
- IO Stall
- 错误处理繁琐
- 无法完全发挥硬件性能
二、 LSMT存储引擎的优势与实现
1. LSMT 与B+Tree的异同
- 在B+Tree 中,数据插入是原地更新的
- B+Tree在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡
- LSMT 与B+Tree可以用统一模型描述
- 从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化
- 工程实践上还是用LSMT来表示一个Append-only和Lazy Compact 的索引树,B+Tree来表示一个lnplace-Update和Instant Compact的索引树
- Append-only 和 Lazy Compact这两个特性更符合现代计算机设备的特性
2. 为什么要采用 LSMT模型?
- 在计算机存储乃至整个工程界都在利用Indirection处理资源的不对称性
- 存储引擎面对的资源不对称性在不同时期是不同的
2.1 HDD时代
- 顺序与随机操作性能不对称
- 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25 倍
2.2 SSD时代
- 顺序写与随机写性能不对称
- 由于SSD随机写会给主控带来GC压力,顺序写吞吐是随机写的6倍
2.3 小结
- HDD 时代,顺序操作远快于随机操作
- SSD时代,顺序写操作远快于随机写操作
- 这二者的共性是顺序写是一个对设备很友好的操作,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写
3. LSMT存储引擎的实现,以 RocksDB为例
- RocksDB是一款十分流行的开源LSMT存储引擎,最早来自Facebook (Meta),应用于MyRocks,TiDB等数据库
- 在字节内部也有Abase,ByteKV,ByteNDB,Bytable等用户
- 因此接下来将会以 RocksDB为例子介绍LSMT存储引擎的经典实现
3.1 LSMT存储引擎的实现- Write
- RocksDB 写入流程主要有两个优化,批量 WAL写入(继承自LevelDB)与并发MemTable更新
- RocksDB在真正执行修改之前会先将变更写入WAL,WAL写成功则写入成功
- 多个写入者会选出一个Leader,由这个Leader来一次性写入 WAL,避免小IO
- 不要求WAL强制落盘(Sync)时,批量提交亦有好处,Leader 可以同时唤醒其余Writer,降低了系统线程调度开销
- 没有批量提交的话,只能链式唤醒
- 链式唤醒加大前台延迟
- 写完WAL还要写MemTable
- RocksDB在继承LevelDB的基础上又添加了并发MemTable写入的优化
- WAL一次性写入完成后,唤醒所有Writer并行写入 MemTable
- 由最后一个完成 MemTable 写入的Writer执行收尾工作
3.2 LSMT存储引擎的实现-Snapshot & SuperVision
- RocksDB的数据由3部分组成,MemTable/ lmmemTable / SST。持有这三部分数据并且提供快照功能的组件叫做SuperVersion
- MemTable和SST的释放依赖于引用计数。对于读取来说,只要拿着SuperVersion,从MemTable 一级一级向下,就能查到记录。拿着SuperVersion不释放,等于是拿到了快照
-
如果所有读者都给SuperVersion的计数加1,读完后再减1,那么这个原子引用计数器就会成为热点。CPU在多核之间同步缓存是有开销的,核越多开销越大
-
为了让读操作更好的scale,RocksDB做了一个优化是Thread Local SuperVersionCache
-
没有 Thread Local 缓存时,读取操作要频繁Acquire和 Release SuperVersion
-
CPU 缓存不友好
- 有Thread Local缓存,读取只需要检查一下SuperVersion并标记 ThreadLocal缓存正在使用即可
- CPU 缓存友好
3.3 LSMT存储引擎的实现- Get & BloomFilter
- RocksDB的读取在大框架上和B+ Tree类似,就是层层向下
- 相对于B+Tree,LSMT点查需要访问的数据块更多。为了加速点查,一般LSMT引擎都会在SST中嵌入BloomFilter,[1,10]表示这个索引块存储数据的区间在1-10之间。查询2,就是顺着标绿色的块往下
3.4 LSMT存储引擎的实现Compact - Level
- Compact在 LSMT中是将Key区间有重叠或无效数据较多的SST进行合并,以此来加速读取或者回收空间。Compact策略可以分为两大类,Level和Tier。下图是Level策略
- Level策略直接来自于LevelDB,也是 RocksDB 的默认策略。每一个层不允许有SST的Key区间重合
3.5 LSMT存储引擎的实现Compact - Tier
- Tier策略允许LSMT每层有多个区间重合的SST
三、 LSMT 模型理论分析
1. Cloud-Native LSMT Storage Engine - HBase
- RocksDB是单机存储引擎,那么现在都说云原生,HBase比 RocksDB就更「云」一些,SST直接存储于HDFS 上
- 二者在理论存储模型上都是LSMT
2. 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:区间查询的记录数量
2.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,失效率为,只有当 BloomFilter 失效时才会访问下一层。因此二者相乘可得读取的开销。注意,这里不乘 1/B 的原因是写入可以批量提交,但是读取的时候必须对齐到最小读取单元尺寸
2.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 策略增加了写放大,降低了读和空间放大
四、 0LSMT存储引擎调优案例与展望
1. LSMT存储引擎调优案例- TerarkDB
- TerarkDB aka LavaKV是字节跳动内部基于RocksDB深度定制优化的自研LSMT存储引擎,其中完全自研的KV分离功能,上线后取得了巨大的收益
- KV分离受启发于论文 WiscKey: Separating Keys from Values in sSD-consciousStorage,概括起来就是Value较长的记录的Value单独存储
2. LSMT存储引擎调优案例 TerarkDB & Abase & ByteGraph
- 图存储场景描述:
- Key size : 20B ~ 30B
- Value size:数十KB级别
- 写多读少
- 收益结论:
- 延迟大幅度降低,长尾消失,扛住了比 RocksDB高50%的负载
3. LSMT存储引擎调优案例- TerarkDB & Flink
- 在字节内部Flink流处理状态存储场景实测
- 收益结论:
- 平均CPU 开销在3个作业上降低了26%~39%
- 峰值cDI, .MA .T作业上降低了26%~39%
- 平均容量开销在3个作业上降低了17%~31.2%
- 直播业务某集群容量不收缩,TerarkDB的schedule TTL GC彻底解决了该问题
4. 存储引擎最新发展趋势–新硬件
- 随着硬件的发展,软件设计也会随着发生改变。近年来,出现了许多新的存储技术,例如SMR
- HDD,Zoned SSD / OpenChannel SSD,PMem等。如何在这些新硬件上设计/改进存储引擎是一大研究热点
5. 存储引擎最新发展趋势-新模型
- 经典LSMT模型是比较简单的,有时候不能应对所有工况,可以提出新的模型来解决问题
6. 存储引擎最新发展趋势-新参数/新工况
- 已有的模型,在新的或者现有工况下,参数设置的不合理,可以通过更精确的参数设置来提升整体性能
课程总结
-
单机数据库的ACID特性依赖于存储引擎
-
LSMT存储引擎的顺序写特性更适合现代计算机体系结构
-
LSMT和B+Tree可以用同一模型描述并互相转化
-
Level Compaction策略,降低了读放大和空间放大,增加了写放大
-
Tier Compaction策略,降低了写放大,增大了读放大和空间放大
-
分布式KV存储,如HBase,背后的理论模型与单机存储引擎RocksDB一样都是LSMT
引用参考
内容主要参考了林源劲老师在「LSMT 存储引擎浅析」课程里所教授的内容,同时也参考了学员手册里第三节的内容,图片来自于老师的PPT,链接如下: