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

217 阅读10分钟

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

今天是大数据专场基础班的第十四次课,主要内容是LSMT 存储引擎浅析,主要分为下面四个板块。

一、 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中全部强依赖于存储引擎

image.png

3.1 ACID是什么/存储引擎哪些组件保障了这些特性?

  • Atomicity

  • Write-Ahead Log(WAL) / Redo Log

  • Consistency(Correctness)依赖于数据库整体

  • lsolation

  • Snapshot /2PL(Phase Lock)

  • Durability

  • Flusher遵循Sync语意

  • 除了保障ACID以外,存储引擎还要负责:

    • 屏蔽IO 细节提供更好的抽象
    • 提供统计信息与Predicate Push Down能力
  • 存储引擎不掌控IO细节,让操作系统接管,例如使用mmap,会有如下问题:

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

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

1. LSMT 与B+Tree的异同

image.png

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

image.png

  • LSMT 与B+Tree可以用统一模型描述
  • 从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化
  • 工程实践上还是用LSMT来表示一个Append-only和Lazy Compact 的索引树,B+Tree来表示一个lnplace-Update和Instant Compact的索引树
  • Append-only 和 Lazy Compact这两个特性更符合现代计算机设备的特性

2. 为什么要采用 LSMT模型?

  • 在计算机存储乃至整个工程界都在利用Indirection处理资源的不对称性
  • 存储引擎面对的资源不对称性在不同时期是不同的

2.1 HDD时代

  • 顺序与随机操作性能不对称
  • 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25 倍

2.2 SSD时代

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

2.3 小结

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

3. LSMT存储引擎的实现,以 RocksDB为例

  • RocksDB是一款十分流行的开源LSMT存储引擎,最早来自Facebook (Meta),应用于MyRocks,TiDB等数据库
  • 在字节内部也有Abase,ByteKV,ByteNDB,Bytable等用户
  • 因此接下来将会以 RocksDB为例子介绍LSMT存储引擎的经典实现

3.1 LSMT存储引擎的实现- 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 LSMT存储引擎的实现-Snapshot & SuperVision

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

image.png

  • 如果所有读者都给SuperVersion的计数加1,读完后再减1,那么这个原子引用计数器就会成为热点。CPU在多核之间同步缓存是有开销的,核越多开销越大

  • 为了让读操作更好的scale,RocksDB做了一个优化是Thread Local SuperVersionCache

  • 没有 Thread Local 缓存时,读取操作要频繁Acquire和 Release SuperVersion

  • CPU 缓存不友好

image.png

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

image.png

3.3 LSMT存储引擎的实现- Get & BloomFilter

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

image.png

3.4 LSMT存储引擎的实现Compact - Level

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

image.png

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

3.5 LSMT存储引擎的实现Compact - Tier

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

image.png

三、 LSMT 模型理论分析

1. Cloud-Native LSMT Storage Engine - HBase

  • RocksDB是单机存储引擎,那么现在都说云原生,HBase比 RocksDB就更「云」一些,SST直接存储于HDFS 上
  • 二者在理论存储模型上都是LSMT

image.png

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

eMNe^{- \frac{M}{N} }是 BloomFilter 的 false positive rate

S:区间查询的记录数量

image.png

2.1 LSMT 模型算法复杂度分析– Level

  • Write:每条记录抵达最底层需要经过 L 次 Compact,每次 Compact Ln 的一个小 SST 和 Ln+1 的一个大 SST。设小 SST 的大小为 1,那么大 SST 的大小则为 T,合并开销是 1+T,换言之将 1 单位的 Ln 的 SST 推到 Ln+1 要耗费 1+T 的 IO,单次 Compact 写放大为 T。每条记录的写入成本为 1/B 次最小单位 IO。三者相乘即得结果

  • Point Lookup:对于每条 Key,最多有 L 个重叠的区间,每个区间都有 BloomFilter,失效率为eMNe^{- \frac{M}{N} },只有当 BloomFilter 失效时才会访问下一层。因此二者相乘可得读取的开销。注意,这里不乘 1/B 的原因是写入可以批量提交,但是读取的时候必须对齐到最小读取单元尺寸

2.2 LSMT模型算法复杂度分析- Tier

  • Write:每条记录抵达最底层前同样要经过 L 次 Compact,每次 Compact Ln 中 T 个相同尺寸的 SST 放到 Ln+1。设 SST 大小为 1,那么 T 个 SST Compact 的合并开销是 T,换言之将 T 单位的 Ln 的 SST 推到 Ln+1 要耗费 T 的 IO,单次 Compact 的写放大为 T / T = 1。每条记录的写入成本为 1/B 次最小单位 IO。三者相乘即得结果

  • Point Lookup:对于每条 Key,有 L 层,每层最多有 T 个重叠区间的 SST,对于整个 SST 来说有 T * L 个可能命中的 SST,乘上 BloomFilter 的失效率即可得结果

  • 总结,Tier 策略降低了写放大,增加了读放大和空间放大,Level 策略增加了写放大,降低了读和空间放大

四、 0LSMT存储引擎调优案例与展望

1. LSMT存储引擎调优案例- TerarkDB

  • TerarkDB aka LavaKV是字节跳动内部基于RocksDB深度定制优化的自研LSMT存储引擎,其中完全自研的KV分离功能,上线后取得了巨大的收益
  • KV分离受启发于论文 WiscKey: Separating Keys from Values in sSD-consciousStorage,概括起来就是Value较长的记录的Value单独存储

image.png

2. LSMT存储引擎调优案例 TerarkDB & Abase & ByteGraph

  • 图存储场景描述:
    • Key size : 20B ~ 30B
    • Value size:数十KB级别
    • 写多读少
  • 收益结论:
    • 延迟大幅度降低,长尾消失,扛住了比 RocksDB高50%的负载

image.png

3. LSMT存储引擎调优案例- TerarkDB & Flink

  • 在字节内部Flink流处理状态存储场景实测
  • 收益结论:
    1. 平均CPU 开销在3个作业上降低了26%~39%
    2. 峰值cDI, .MA .T作业上降低了26%~39%
    3. 平均容量开销在3个作业上降低了17%~31.2%
    4. 直播业务某集群容量不收缩,TerarkDB的schedule TTL GC彻底解决了该问题

4. 存储引擎最新发展趋势–新硬件

  • 随着硬件的发展,软件设计也会随着发生改变。近年来,出现了许多新的存储技术,例如SMR
  • HDD,Zoned SSD / OpenChannel SSD,PMem等。如何在这些新硬件上设计/改进存储引擎是一大研究热点

5. 存储引擎最新发展趋势-新模型

  • 经典LSMT模型是比较简单的,有时候不能应对所有工况,可以提出新的模型来解决问题

6. 存储引擎最新发展趋势-新参数/新工况

  • 已有的模型,在新的或者现有工况下,参数设置的不合理,可以通过更精确的参数设置来提升整体性能

课程总结

  • 单机数据库的ACID特性依赖于存储引擎

  • LSMT存储引擎的顺序写特性更适合现代计算机体系结构

  • LSMT和B+Tree可以用同一模型描述并互相转化

  • Level Compaction策略,降低了读放大和空间放大,增加了写放大

  • Tier Compaction策略,降低了写放大,增大了读放大和空间放大

  • 分布式KV存储,如HBase,背后的理论模型与单机存储引擎RocksDB一样都是LSMT

引用参考

内容主要参考了林源劲老师在「LSMT 存储引擎浅析」课程里所教授的内容,同时也参考了学员手册里第三节的内容,图片来自于老师的PPT,链接如下:

  1. 【大数据专场 学习资料五】第四届字节跳动青训营 - 掘金 (juejin.cn)
  2. LSMT 存储引擎浅析 - 林源劲 - ppt.pptx - 飞书文档 (feishu.cn)