LSMT存储引擎浅析 | 青训营笔记

323 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第14天


0x00 LSMT与存储引擎

0.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等。

0.2 LSMT 是什么

一言以蔽之,通过 Append-only Write + 择机 Compact 来维护索引树的结构。

14_LSMT工作模式.png

0.2 存储引擎是什么

数据库分为计算层和存储层(存储引擎层)

  • 计算层主要负责SQL解析/查询优化/计划执行
  • 数据库著名的ACID特性,在MySQL中全部强依赖于存储引擎
    • 存储引擎保障ACID
    • 屏蔽IO细节提供更好的抽象
    • 提供统计信息与 Predicate Push Down 能力

14_存储引擎.png


0x01 LSMT存储引擎的优势与实现

1.1 LSMT与B+Tree的异同

  • 在B+Tree中,数据插入是原地更新的
  • B+Tree在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡

14_LSMT与B+Tree异同.png

  • 在 B+Tree 中,数据插入是原地更新的,装有 (10, 20, 30, 40) 的节点在插入和分裂后,原节点覆写成 (10, 15)。此外,B+Tree 在发生不平衡或者节点容量到达阈值后,必须立即进行分裂来平衡。
  • 反观 LSMT,数据的插入是追加的(Append-only),当树不平衡或者垃圾过多时,有专门 Compact 线程进行 Compact,可以称之为延迟(Lazy)的。
  • LSMT与B+Tree可以用统一模型描述
  • 从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化
  • 工程实践上还是用 LSMT 来表示一个 Append-only 和 Lazy Compact 的索引树, B+Tree 来表示一个 Inplace-Update 和 Instant Compact 的索引树
  • Append-only 和 Lazy Compact 这两个特性更符合现代计算机设备的特性 14_LSMT异同.jpg

1.2 为什么采用LSMT模型

在计算机存储乃至整个工程界都在利用 Indirection 处理资源的不对称性,比方说内存价格上升了,就要想办法 offload 数据到别的廉价介质上。存储引擎面对的资源不对称性在不同时期是不同的。

  1. HDD 时代:
    • 顺序操作和随机操作的不对称性
    • 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写顺序写吞吐是随机读的25倍。
  2. SSD 时代:
    • 顺序写和随机写的不对称性
    • 由于SSD随机写会给主控带来GC压力,顺序写吞吐是随机写的6倍。

二者共性是顺序写对设备操作友好,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写。

1.3 LSMT 存储引擎实现(RocksDB)

RocksDB 是一款十分流行的开源 LSMT 存储引擎,最早来自 Facebook(Meta),应用于 MyRocks,TiDB。

1.3.1 RocksDB - Write

  • RocksDB 写入流程主要有两个优化,批量WAL写入(继承自LevelDB)与并发MemTable更新
  • RocksDB 在真正执行修改之前会先将变更写入WAL,WAL写成功则写入成功。

14_Write.png

1.3.1.1 写WAL

  • RocksDB WAL 写入流程继承自 LevelDB。LevelDB 在 WAL 写入主要做的一个优化是多个写入者会选出一个 Leader,由这个 Leader 来一次性写入。这样的好处在于可以批量聚合请求,避免频繁提交小 IO。
  • 但很多业务其实不会要求每次 WAL 写入必须落盘,而是写到 Kernel 的 Page Cache 就可以,Kernel 自身是会聚合小 IO 再下刷的。这时候,批量提交的好处就在于降低了操作系统调度线程的开销。

批量提交时,Leader 可以同时唤醒其余 Writer。

14_Write1.jpeg 如果没有批量提交就只能链式唤醒了。

14_Write2.jpeg

1.3.1.2 写MemTable

  • 写完 WAL 实际还要写 MemTable
  • RocksDB 在 LevelDB 的基础上主要又添加了并发 MemTable 写入的优化

14_Write3.png

  • WAL一次性写入完成后,唤醒所有Writer并行写入MemTable
  • 由最后一个完成MemTable写入的Writer执行收尾工作

14_Write4.jpeg

1.3.2 RocksDB - Snapshot & SuperVision

  • RocksDB 的数据由 3 部分组成,MemTable / ImmemTable / SST。直接持有这三部分数据并且提供快照功能的组件叫做 SuperVersion。
  • RocksDB 的 MemTable 和 SST 的释放与删除都依赖于引用计数,SuperVersion 不释放,对应的 MemTable 和 SST 就不会释放。对于读取操作来说,只要拿着这个 SuperVersion,从 MemTable 开始一级一级向下,就能查询到记录。那么拿着 SuperVersion 不释放,等于是拿到了快照。

14_Snapshot.jpeg

  • 如果所有读者开始操作前都给 SuperVersion 的计数加 1,读完后再减 1,那么这个原子引用计数器就会成为热点。CPU 在多核之间同步缓存是有开销的,核越多开销越大。
  • 没有 Thread Local 缓存时,读取操作要频繁 Acquire 和 Release SuperVersion

14_Snapshot1.jpeg

  • 有 Thread Local 缓存时,读取只需要检查一下 SuperVersion 并标记缓存正在使用即可,可以看出多核之间的交互就仅剩检查 SuperVersion 缓存是否过期了。

14_Snapshot2.jpeg

1.3.3 Get & BloomFilter

  • RocksDB 的读取在大框架上和 B+ Tree 类似,就是层层向下。
  • 相对于B+Tree,LSMT点查需要访问的数据块更多。为了加速点查,一般LSMT引擎都会在SST中嵌入BloomFilter。

14_Get&BloomFilter.jpeg

1.3.4 Compact - Level

  • Compact 在 LSMT 中是将 Key 区间有重叠或无效数据较多的 SST 进行合并,以此来加速读取或者回收空间。Compact 策略可以分为两大类。
  • Level 策略直接来自于 LevelDB,也是 RocksDB 的默认策略。每一个层不允许有 SST 的 Key 区间重合。

14_Compact.png

1.3.5 Compact - Tier

  • Tier 策略允许 LSMT 每层有多个区间重合的 SST

14_Tier.png


0x02 LSMT模型理论分析

2.1 Cloud-Native LSMT Storage Engine - HBase

RocksDB 是单机存储引擎,那么现在都说云原生,HBase 比 RocksDB 就更「云」一些,SST 直接存储于 HDFS 上,Meta 信息 RocksDB 自己管理维护于 Manifest 文件,HBase 放置于 ZK。二者在理论存储模型上都是 LSMT。

14_HBase.png

2.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 e−MNe^{- \frac{M}{N} } e−NM​是 BloomFilter 的 false positive rate S:区间查询的记录数量

14_算法复杂度分析.png