LSMT存储引擎浅析| 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第3天,主要是对上课时的内容进行汇总,通过对知识的再次学习,在实现简易分布式存储系统时,可以借鉴DataNode本地存储的一些设计。
LSMT与存储引擎介绍
LSMT历史
- LSMT是Log-Structured Merge-Tree 的缩写,由Patrick O'Neil etc.在1996年的论文,The Log-Structed Merge-Tree(LSM-Tree),提出。
- B-Tree出现较早,在1970年由Bayer,R.;McCreight,E.提出。
- 早期的数据库系统一般都采用B-Tree家族作为索引,例如MySQL。2000年后诞生的数据库大多采用LSMT索引,例如Google Bigtable,HBase,Canssandra等。
LSMT是什么
就是通过Append-only Write + 择机Compact 来维护结构的索引树。
存储引擎是什么
以单机数据库MySQL为例, 大致可以分为:
- 计算层,主要负责SQL解析/查询优化/计划执行
- 存储层(存储引擎层) ACID特性,在mysql中全部强依赖于存储引擎
- 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
- 错误处理繁琐
- 无法完全发挥硬件性能
LSMT存储引擎的优势与实现
LSMT与B+Tree的异同
- 在B+Tree中,数据插入是原地更新的
- B+Tree在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡
- LSMT与B+Tree可以用统一模型描述
- 从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化
- 工程实践上还是用LSMT来表示一个Append-only和Lazy Compact 的索引树,B+Tree来表示一个Implace-Update 和 Instant Compact的索引树。
- Append-only 和 lazy Compact这两个特性更符合现代计算机设备的特性。
为什么要采用LSMT模型
- 在计算机存储乃至整个工程界都在利用Indirection 处理资源的不对称性
- 存储引擎面对的资源不对称性在不同时期是不同的
- HDD时代
- 顺序与随机操作性能不对称,由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25倍。
- SSD时代
- 顺序写与随机写性能不对称,由于SSD随机写会对主控带来GC压力,顺序写吞吐是随机写的6倍。
这二者的共性是顺序写是一个对设备很友好的操作,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写。
LSMT存储引擎的实现,以RocksDB为例
- RocksDB是一款十分流行的开源LSMT存储引擎,最早来自Facebook,应用于MyRocks、TiDB等数据库。
LSMT 存储引擎的实现-Write
- RocksDB写入流程主要有两个优化,批量WAL写入(继承自LevelDB)与并发MemTable更新
- RocksDB在真正执行修改之前会先将变更写入WAL,WAL写成功则写入成功。
- 多个写入者会选出一个Leader,由这个Leader来一次性写入WAL,避免小IO
- 不要求WAL强制落盘(Sync)时,批量提交亦有好处,Leader可以同时唤醒其余Writer,降低了系统线程调度开销。
- 没有批量提交的话,只能链式唤醒
- 链式唤醒加大前台延迟。
- 写完WAL还有写MenTable。
- RocksDB在继承LevelDB的基础上又添加了并发MenTable写入的优化。
- WAL一次性写入完成后,唤醒所有Writer并行写入MenTable
- 由最后一个完成MemTable写入的Writer执行收尾工作。
LSMT存储引擎的实现-Snapshot&SuperVision
- RocksDB的数据由3部分组成,MemTable/ImmemTable/SST。持有这三部分数据并且提供快照功能的组件叫做SuperVersion。
- MemTable和SST的释放依赖于引用计数。对于读取来说,只要拿着SuperVersion,从MemTable一级一级向下,就能查到记录。拿着SuperVersion不释放,等于是拿到了快照。
- 如果所有读者都给SuperVersion的计数都加1,读完后再减1,那么这个原子引用计数器就会成为热点。CPU在多核之间同步缓存是有开销的,核越多开销越大。
- 为了让读操作更好的Scale,RocksDB做了一个优化是Thread LocalSuperVersion Cache
- 没有Thread Local缓存时,读取操作要频繁Acquire和Release SuperVersion CPU缓存不友好
- 有Thread Local缓存,读取只需要检查一下SuperVersion并标记ThreadLocal缓存正在使用即可 CPU缓存友好
LSMT存储引擎的实现-Get&BloomFilter
- RocksDB的读取在大框架上和B+Tree类似,就是层层向下。
- 相对于B+Tree,LSMT点查需要访问的数据块更多。为了加速点查,一般LSMT引擎都会在SST中嵌入BloomFilter。
LSMT存储引擎的实现 Compact-Level
- Compact在LSMT中是将Key区间有重叠或无效数据较多的SST进行合并,以此来加速读取或者回收空间。Compact策略可以分为两大类,Level和Tier。
- Level策略直接来自于LevelDB,也是RocksDB的默认策略。每一个层不允许有SST的Key区间重合。
- Tier策略允许LSMT每层有多个区间重合的SST