LSMT 存储引擎浅析 - 复习笔记
这是我参与「第四届青训营 」笔记创作活动的的第15天!
1. LSMT与存储引擎介绍
1.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等。
1.2 LSMT是什么
一言以蔽之,通过Append-only Write+择机Compact来维护结构的索引树。
1.3 存储引擎是什么
以单机数据库MySQL为例,大致可以分为:
- 计算层
- 存储层(存储引擎层)
计算层主要负责SQL解析/查询优化/计划执行。
数据库著名的ACID特性,在MySQL中全部强依赖于存储引擎。
ACID是什么/存储引明您业进考性?
- Atomicity
Write-Ahead Log(WAL)/Redo Log - Consistency(Correctness)
依赖于数据库整体 - Isolation
Snapshot/2PL(Phase Lock) - Durability
Flusher 遵循Sync 语意
除了保障ACID以外,存储引擎还要负责:
- 屏蔽IO细节提供更好的抽象
- 提供统计信息与Predicate Push Down能力
存储引擎不掌控IO细节,让操作系统接管,例如使用mmap,会有如下问题:
- 落盘时机不确定造成的事务不安全
- IO Stall
- 错误处理繁琐
- 无法完全发挥硬件性能
2. LSMT 存储引擎的优势与实现
2.1 LSMT 与 B+Tree 的异同
- 在B+Tree中,数据插入是原地更新的
- B+Tree在发生不平衡或者节点容量到达阔值后,必须立即进行分裂来平衡
- LSMT与B+Tree可以用统一模型描述
- 从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化
- 工程实践上还是用LSMT来表示一个Append-only和Lazy Compact的素引树,B+Tree来表示一个Inplace-Update 和Instant Compact的索引树。
- Append-only 和Lazy Compact 这两个特性更符合现代计算机设备的特性。
2.2 为什么要采用LSMT模型
- 在计算机存储乃至整个工程界都在利用Indirection处理资源的不对称性
- 存储引擎面对的资源不对称性在不同时期是不同的
HDD时代
- 顺序与随机操作性能不对称
- 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25倍。
SSD时代
- 顺序写与随机写性能不对称
- 由于SSD随机写会给主控带来GC压力,顺序写吞吐是随机写的6倍。
小结:
- HDD时代,顺序操作远快于随机操作
- SSD时代,顺序写操作远快于随机写操作
- 这二者的共性是顺序写是一个对设备很友好的操作,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写。
2.3 LSMT 存储引擎的实现,以RocksDB为例
- RocksDB 是一款十分流行的开源LSMT存储引擎,最早来自Facebook(Meta),应用于MyRocks,TiDB 等数据库。
- 在字节内部也有Abase,ByteKV,ByteNDB,Bytable等用户。
- 因此接下来将会以RocksDB为例子介绍LSMT存储引擎的经典实现。
2.3.1 LSMT 存储引擎的实现-Write
- RocksDB写入流程主要有两个优化,批量WAL写入(继承自LevelDB)与并发 MemTable更新
- RocksDB在真正执行修改之前会先将变更写入WAL,WAL写成功则写入成功。
- 多个写入者会选出一个Leader,由这个Leader来一次性写入WAL,避免小IO。
- 不要求WAL强制落盘(Sync)时,批量提交亦有好处,Leader可以同时唤醒其余Writer,降低了系统线程调度开销。
- 写完WAL还要写 MemTable。
- RocksDB在继承LevelDB的基础上又添加了并发 MemTable写入的优化。
2.3.2 LSMT 存储引擎的实现-Snapshot&SuperVision
- RocksDB的数据由3部分组成,Mem Table/ImmemTable/SST。持有这三部分数据并且提供快照功能的组件叫做 SuperVersion。
- MemTable和SST的释放依赖于引用计数。对于读取来说,只要拿着SuperVersion,从MemTable一级一级向下,就能查到记录。拿着 SuperVersion不释放,等于是拿到了快照。
- 如果所有读者都给 SuperVersion的计数加1,读完后再减1,那么这个原子引用计数器就会成为热点。CPU在多核之间同步缓存是有开销的,核越多开销越大。
- 为了让读操作更好的 scale,RocksDB做了一个优化是Thread Local SuperVersion Cache
- 没有Thread Local缓存时,读取操作要频繁Acquire和Release SuperVersion
- CPU缓存不友好
- 有Thread Local缓存,读取只需要检查一下SuperVersion 并标记 Thread Local 缓存正在使用即可
- CPU缓存友好
2.3.3 LSMT 存储引擎的实现-Get&BloomFilter
- RocksDB的读取在大框架上和B+Tree类似,就是层层向下。
- 相对于B+Tree,LSMT点查需要访问的数据块更多。为了加速点查,一般LSMT引擎都会在SST中嵌入BloomFilter。
[1,10]表示这个索引块存储数据的区间在1-10之间。查询2,就是顺着标绿色的块往下。
2.3.4 LSMT 存储引擎的实现 Compact-Level
- Compact在LSMT中是将Key 区间有重叠或无效数据较多的SST进行合并,以此来加速读取或者回收空间。Compact 策略可以分为两大类,Level和Tier。下图是Level策略,
- Level 策略直接来自于LevelDB,也是RocksDB的默认策略。每一个层不允许有SST的Key区间重合。
2.3.5 LSMT 存储引擎的实现 Compact-Tier
- Tier 策略允许LSMT每层有多个区间重合的SST
3. LSMT楼型理论分析
3.1 Cloud-Native LSMT Storage Engine-HBase
- RocksDB 是单机存储引擎,那么现在都说云原生,HBase比RocksDB 就更“云”一些,SST 直接存储于 HDFS 上。
- 二者在理论存储模型上都是 LSMT。
3.2 LSMT 模型算法复杂度分析
- T:size ratio,每层LSMT比上一层大多少,L0大小为1,则L1大小为T,L2为TA2,以此类推
- L:level num,LSMT层数
- B:每个最小的IO单位能装载多少条记录
- M:每个BloomFilter 有多少bits
- N:每个BloomFilter 生成时用了多少条Key
- S:区间查询的记录数量
- Write
每条记录抵达最底层需要经过L次Compact,每次Compact Ln的一个小SST和Ln+1的一个大SST。
设小SST的大小为1,那么大SST的大小则为T,合并开销是1+T,换言之将1单位的Ln的SST推到Ln+1要耗费1+T的l0,单次Compact写放大为T。
每条记录的写入成本为1/B次最小单位IO。
O(Write_Level)= L * T * 1/ B = T * L / B - Point Lookup
对于每条Key,最多有L个重叠的区间。
每个区间都有BloomFilter,失效率为e ^(-M/N),只有当BloomFilter失效时才会访问下一层。
O(PointLookup_Level)=L * e ^(-M/N)
注意,这里不乘1/B系数的原因是写入可以批量提交拉低成本,但是读取的时候必须对齐到最小读取单元尺寸。
3.3LSMT 模型算法复杂度分析-Tier
- Write
每条记录抵达最底层前同样要经过L次Compact,每次Compact Ln中T个相同尺寸的SST放到Ln+1。
设SST大小为1,那么T个SST Compact的合并开销是T,换言之将T单位的Ln的SST推到Ln+1要耗费T的l0,单次Compact的写放大为T/T=1。
每条记录的写入成本为1/B次最小单位IO。
O(Write_Tier)=L11/B=L/B - Point Lookup
对于每条Key,有L层。
每层最多有T个重叠区间的SST,对于整个SST来说有T * L个可能命中的SST,乘上BloomFilter的失效率,e^(-M/N),可得结果。
O(PointLookup Tier)=L*T*e(-M/N)=T*L*e^(-M/N)
注意,这里不乘1/B系数的原因是写入可以批量提交拉低成本,但是读取的时候必须对齐到最小读取单元尺寸。
3.4 LSMT模型算法复杂度分析
Online Quiz
- Short Range Scan 复杂度是如何推导的?
- Space Amplification 复杂度是如何推导的?
总结,Tier 策略降低了写放大,增加了读放大和空间放大,Level 策略增加了写放大,降低了读和空间放大。
4. LSMT存储引擎调优案例与展望
4.1 LSMT 存储引擎调优案例-TerarkDB
- TerarkDB aka LavaKV 是字节跳动内部基于RocksDB深度定制优化的自研LSMT存储引擎,其中完全自研的KV分离功能,上线后取得了巨大的收益。
- KV分离受启发于论文WiscKey:Separating Keys from Values in SSD-conscious Storage,概括起来就是Value 较长的记录的Value单独存储。
4.2 LSMT 存储引擎调优案例TerarkDB&Abase&ByteGraph
图存储场景描述:
Key size:20B~30B
Value size:数十KB级别
写多读少
收益结论:
延迟大幅度降低,长尾消失,扛住了比RocksDB高50%的负载。
4.3 LSMT 存储引擎调优案例-TerarkDB&Flink
在字节内部Flink 流处理状态存储场景实测
收益结论:
- 平均CPU开销在3个作业上降低了26%~39%
- 峰值CPU开销在广告作业上有明显的收益,降低了67%
- 平均容量开销在3个作业上降低了17%~31.2%
- 直播业务某集群容量不收缩,TerarkDB的 schedule TTL GC彻底解决了该问题
4.4 存储引擎最新发展趋势-新硬件
- 随着硬件的发展,软件设计也会随着发生改变。近年来,出现了许多新的存储技术,例如SMR HDD,Zoned SSD/OpenChannel SSD,PMem等。如何在这些新硬件上设计/改进存储引擎是一大研究热点。
e.g.MatrixKV:Reducing Write Stalls and Write Amplification in LSM-tree Based KV Stores with Matrix Container in NVM
这篇论文中的设计将L0整个搬进了PMem,降低了写放大。
4.5 存储引擎最新发展趋势-新模型
- 经典LSMT模型是比较简单的,有时候不能应对所有工况,可以提出新的模型来解决问题。
e.g.WiscKey:Separating Keys from Values in SSD-conscious Storage
通过额外增加一个Value Store来存储大Value的记录来降低总体写放大。
e.g.REMIX:Efficient Range Query for LSM-trees
通过额外增加一种SST的类型来加速范围查询的速度
4.6 存储引擎最新发展趋势-新参数/新工况
- 已有的模型,在新的或者现有工况下,参数设置的不合理,可以通过更精确的参数设置来提升整体性能。
e.g.The Log-Structured Merge-Bush&the Wacky Continuum
在最后一层使用Level Compaction,之上使用Tier Compaction,通过在除了最后一层以外的SST加大BloomFilter的bits数来规避 Tier Compaction带来的点查劣化。