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

70 阅读5分钟

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

这是我参与「第四届青训营 -大数据场」笔记创作活动的第19天

一、LSMT 与存储引擎介绍

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

2. LSMT 是什么

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

image.png

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 能力

存储引擎不掌控lO细节,让操作系统接管,例如使用mmap,会有如下问题:

  • 落盘时机不确定造成的事务不安全
  • IO Stall
  • 错误处理繁琐
  • 无法完全发挥硬件性能

二、LSMT 存储引擎的优势与实现

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. 为什么要采用 LSMT 模型

  • 在计算机存储乃至整个工程界都在利用 lndirection 处理资源的不对称性
  • 存储引擎面对的资源不对称性在不同时期是不同的
2.1 HDD 时代
  • 顺序与随机操作性能不对称
  • 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25倍。

image.png

2.2 SSD 时代
  • 顺序写与随机写性能不对称
  • 由于SSD随机写会给主控带来GC压力,顺序写吞吐是随机写的6倍。

image.png

2.3 小结
  • HDD 时代,顺序操作远快于随机操作
  • SSD 时代,顺序写操作远快于随机写操作
  • 这二者的共性是顺序写是一个对设备很友好的操作,LSMT 符合这一点,而 B+Tree 依赖原地更新,导致随机写。

3. LSMT 存储引擎的实现

以 RocksDB 为例

  • RocksDB 是一款十分流行的开源LSMT存储引擎,最早来自Facebook(Meta),应用于 MyRocks, TiDB 等数据库。
  • 在字节内部也有Abase, ByteKV, ByteNDB, Bytable等用户。
  • 因此接下来将会以 RocksDB 为例子介绍 LSMT 存储引擎的经典实现。
3.1 Write
  • RocksDB 写入流程主要有两个优化,批量WAL写入(继承自LevelDB)与并发 MemTable 更新
  • RocksDB 在真正执行修改之前会先将变更写入 WAL,WAL 写成功则写入成功。

image.png

  • 多个写入者会选出一个 Leader,由这个 Leader 来一次性写入 WAL,避免小 IO。
  • 不要求 WAL 强制落盘(Sync)时,批量提交亦有好处,Leader 可以同时唤醒其余 Writer,降低了系统线程调度开销。

image.png

  • 没有批量提交的话,只能链式唤醒。
  • 链式唤醒加大前台延迟。

image.png

  • 写完 WAL 还要写 MemTable。
  • RocksDB 在继承 LevelDB 的基础上又添加了并发 MemTable 写入的优化。

image.png

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

image.png

3.2 Snapshot & SuperVision
  • RocksDB 的数据由 3 部分组成,MemTable/ ImmemTable / SST。持有这三部分数据并且提供快照功能的组件叫做 SuperVersion。
  • MemTable 和 SST 的释放依赖于引用计数。对于读取来说,只要拿着 SuperVersion,从MemTable 一级一级向下,就能查到记录。拿着 SuperVersion 不释放,等于是拿到了快照。

image.png

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

image.png

  • 有 Thread Local 缓存,读取只需要检查一下 SuperVersion 并标记Thread Local缓存正在使用即可
  • CPU缓存友好

image.png

3.3 Get & BloomFilter
  • RocksDB 的读取在大框架上和 B+Tree 类似,就是层层向下。
  • 相对于 B+Tree,LSMT 点查需要访问的数据块更多。为了加速点查,一般 LSMT 引擎都会在 SST 中嵌入 BloomFilter。
  • [1,10] 表示这个索引块存储数据的区间在 1-10 之间。查询2,就是顺着标绿色的块往下。

image.png

3.4 Compact - Level
  • Compact 在 LSMT 中是将 Key 区间有重叠或无效数据较多的 SST 进行合并,以此来加速读取或者回收空间。Compact 策略可以分为两大类,Level 和 Tier。下图是 Level 策略

image.png

  • Level 策略直接来自于 LevelDB, 也是 RocksDB 的默认策略。每一个层不允许有 SST 的 Key 区间重合。
3.5 Compact - Tier

image.png

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