这是我参与「第四届青训营 」笔记创作活动的第3天
LSMT和存储引擎介绍
历史
LSMT是什么
通过Append-only Write +择机Compact来维护结构的索引树
存储引擎是什么
以单机数据库MySQL为例,大致可以分为:
- 计算层
- 存储层(存储引擎层)
计算层主要负责SQL解析/查询优化/计划执行。数据库著名的 ACID特性,在MySQL中全部强依赖于存储引擎。 ACID是什么/存储引擎哪些组件保障了这些特性?
- Atomicity
- Write-Ahead Log(WAL)/ Redo Log
- Consistency(Correctness)
- 依赖于数据库整体
- lsolation
- Snapshot / 2PL(Phase Lock)
- Durability
- Flusher遵循Sync语意
LSMT 存储引擎的优势与实现
LSMT 与 B+Tree 的异同
- 在B+Tree 中,数据插入是原地更新的
- B+Tree在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡
- LSMT与B+Tree 可以用统一模型描述
- 从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化
为什么采用LSMT
- HDD时代,顺序操作远快于随机操作
- SSD时代,顺序写操作远快于随机写操作 这二者的共性是顺序写是一个对设备很友好的操作,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写。
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执行收尾工作
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并标记 Thread.ocal 缓存正在使用即可 ——CPU 缓存友好
Get & BloomFilter
- RocksDB的读取在大框架上和B+ Tree类似,就是层层向下
- 相对于B+Tree,LSMT点查需要访问的数据块更多。为了加速点查,一般LSMT引擎都会在SST中嵌入 BloomFilter
Compact-Level
- Compact在 LSMT中是将Key区间有重叠或无效数据较多的SST进行合并,以此来加速读取或者回收空间。Compact策略可以分为两大类,Level和Tier。下图是 Level策略
- Level策略直接来自于LeveIDB,也是RocksDB 的默认策略。每一个层不允许有SST的Key区间重合