LSMT存储引擎解析 | 青训营笔记
这是我参与「第四届青训营」笔记创作活动的的第13天。
一、课程概述
- 介绍LSMT存储引擎的背景、优势与实现
- 介绍LSMT等模型理论
- 介绍LSMT存储引擎调优
二、详细内容
1. LSMT存储引擎
1.1 LSMT的历史
- LSMT是log-structured merge-tree的缩写
- 相较而言B-Tree早得多
- 早起数据库一般采用B-Tree,近期则是LSMT为多
1.2 LSMT基础
- Append-only write+择机compact来维护结构的索引树
- manifest log传输给write-ahead-log WAL
- 写入memtable
- 当memtable满后 flush进L0 cache生成SST file
- compaction(merge sort,remove)
1.3 存储引擎是什么
- 单机数据库MySQL大致分为计算层和存储层
- 计算层负责SQL解析、查询优化、计划执行
- 数据库ACID特性强依赖于存储引擎
- Atomicity原子性:依赖于WAL/Redo Log
- Consistency(correctness):依赖于数据库整体
- Isolation:存储引擎snapshot/2-phase-lock
- Durability:flusher遵循sync语义
- 此外存储引擎还要负责
- 屏蔽IO细节提供更好抽象
- 存储引擎不掌控IO细节,操作系统接管,例如使用mmap的问题:
- 落盘时机不确定造成事务不安全
- IO stall:用户态程序无法插手,不可控
- 错误处理繁琐:硬件错误用户无法获知,需要完整性校验
- 无法完全发挥硬件性能:要切换到内核态
- 存储引擎不掌控IO细节,操作系统接管,例如使用mmap的问题:
- 提供统计信息与predicate push down能力:parquet只需要部分column时下推给存储引擎读取部分即可
- 屏蔽IO细节提供更好抽象
2.LSMT的优势和实现
2.1 LSMT与B+树存储
- 在B+树中数据插入原地更新
- B+树不平衡或节点容量阈值后必须立即分裂来平衡
- LSMT与B+树统一模型,二者可以互相转化
- LSMT表示append-only和lazy compact的索引树
- B+树表示inplace-update和instant compact的索引树
- append-only和lazy compact更符合现代计算机设备特性
2.2 为何要采用LSMT模型
- 计算机存储/工程界使用indirection处理资源的不对称性
- 存储引擎面对的资源不对称性在不同时期不同
- HDD时代:顺序与随机操作性能不对称;机械硬盘需要磁盘旋转和机械臂移动进行读写,顺序写吞吐是随机读的25倍;
- SSD时代:顺序写与随机写性能不对称;由于SSD随机写会对主控带来GC压力,顺序写吞吐是随机写6倍;
- 顺序写对设备友好,LSMT符合顺序写;B+树依赖原地更新导致大量随机写(不友好)
2.3 LSMT存储引擎实现,以RocksDB为例
2.3.1 RocksDB写实现
- WAL
- 需要写WAL(重启时可以保证原子性),多个写入者选出一个Leader,由Leader一次性写入WAL,避免小IO;写完后唤醒所有writer
- 不要求WAL强制落盘(sync)时,批量提交亦有好处;没有批量提交时只能链式唤醒,加大前台延迟
- MemTable
- 在LevelDB基础上添加并发Memtable写入优化
- WAL一次性写入完成后唤醒所有writer并行写入MemTable
- 由最后一个完成MemTable写入的writer执行收尾工作(退出write group)
2.3.2 RocksDB snapshot和supervision
- 数据由MemTable/ImmemTable/SST组成,持有三部分数据并且提供快照功能的组件叫做supervision
- MemTable和SST释放依赖于引用计数,对于读取只要有supervision从memtable一级一级向下就能查到记录;拿着supervision不释放等于拿到快照。
- 如果所有读者给supervision计数+1读完-1,原子引用计算器成为热点;使用了thread local cache
- 没有时,读取频繁获取和释放supervision,对CPU缓存不友好
- 使用thread local cache缓存,只需要检查supervision的version没过期,并标记thread local cache正在使用即可;对CPU缓存友好
- 若superversion过期需要重新生成,当更新时需要标记所有人持有的superversion失效
2.3.3 RocksDB Get和BloomFilter
- 读取上和B+树类似,层层向下
- LSMT点查需要访问的数据块更多,加速点查一般LSTM会在SST中引用BloomFilter
- BloomFilter可以保证数据不在该块中,但只能大概率保证数据在该块中
2.3.4 Compact
- Compact在LSTM中将Key区间有重叠或无效数据较多的SST进行合并以此加速读取或回收空间
- 策略:Level和Tier
- Level 策略来自于LevelDB,每层不允许SST的key区间重合
- 例如用户写0-100 SST,和原有0-100SST merge形成一个新的0-100的大SST(小SST和大SST合并)
- 写放大问题/不平均
- Tier策略允许LSMT每层有多个区间重合的SST
- 当本层重合SST区间或总内存大小到达上限,选择多个重合SST向下推形成新的SST
- 理论上每层SST区间大小接近,merge效率高
- 读放大增加换取写放大减小:更多重合SST需要更多读取
3. LSTM理论分析
3.1 HBase:cloud-native
3.2 LSMT模型算法复杂度分析
- T:size ratio即每层比上层大多少,L0为1,L1为T...
- L:level num,LSMT层数
- B:每个最小IO单位能装载多少记录
- M:每个BloomFilter有多少Bits
- N:每个BloomFilter生成用了多少key
- S:区间查询的记录数量
- e^(-M/N):bloom filter失效率
3.2.1 Level
- 写操作
- 每条记录需要经过L次Compact,每次Compact Ln的一个小SST和Ln+1的大SST
- 设小SST大小为1,大SST为T,合并1+T,单次写放大为T
- 每条记录写入成本为1/B次最小单位IO
- O(write_level)= L*T*1/B = T*L/B
- point lookup
- 每条key最多L个重叠区间
- bloom filter失效率为e^(-M/N),失效时访问下一层
- O(pointLookup_level)=L*e^(-M/N)
- 不乘1/B因为写入可以批量提交拉低成本,但读取时必须对其最小读取单元尺寸
3.2.2 Tier
- 写操作
- 每条记录需要经过L次Compact,每次Compact Ln中T个相同尺寸的SST到Ln+1
- SST大小为1,T个合并开销为T;
- 将T单位Ln SST推到Ln+1需要T的IO,单次写放大为1
- 每条记录写入成本为1/B次最小单位IO
- O(write_level)= L*1*1/B = L/B
- point lookup
- 每条key有L层
- 每层最多有T个重叠区间SST,对于整体来说有T*L个可能命中的SST
- bloom filter失效率为e^(-M/N),失效时访问下一层
- O(pointLookup_level)=T*L*e^(-M/N)
- 不乘1/B因为写入可以批量提交拉低成本,但读取时必须对其最小读取单元尺寸
3.2.3 其他复杂度
- Short Range Scan复杂度:level-O(L);Tier-O(T*L)
- space amplification复杂度: level-上层都是垃圾 O(T+1/T);Tier:O(T)
3.2.4 总结
- Tier策略降低了写放大,增加了读放大和空间放大;
- Level策略增加了写放大,降低了读和空间放大;
4. 实例
4.1 TerarkDB
- KV分离:分为value不同长度;value长的记录单独存储(避免compaction频繁挪动)
- 小key大value,写多读少,延迟大幅度降低,长尾消失,可以接受比rocksDB高50%负载
- Flink流处理状态存储
- 平均CPU开销降低26%~39%
- 峰值CPU开销降低67%
- 平均容量降低17%~31.2%
4.2 发展趋势
- 新硬件
- 新存储技术:SMR HDD/Zoned SSD/OpenChannel SSD,PMem
- 如何在新硬件上设计/改进存储引擎是热点
- L0->PMem降低写放大
- 新模型
- LSMT不能应对所有工况
- WiscKey通过额外增加value store存储大value记录降低总体写放大
- REMIX
- 新参数/新工况
- 在现有模型工况下进行参数调残
- LSMB在最后层使用level,上层tier,通过在最后一层外SST加大bloomFilter的bits数规避Tier Compaction带来的点查劣化
三、个人总结
这节课学习了LSMT存储引擎的背景、优势、理论与实现,及其调优的过程。由于我没有接触过相关算法,进一步比对B+树学习相关算法是非常必要的。