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

321 阅读11分钟

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

一、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 来维护结构的索引树。

5b1034311fd9b60bdd0198cbfac2582.jpg

3.存储引擎是什么?
  • 以单机数据库 MySQL,为例大致可以分为:
    • 计算层
    • 存储层(存储引擎层)

计算层主要负责SQL解析/查询优化/计划执行。

数据库著名的ACID 特性,在MySQL 中全部强依赖于存储引擎。

45794d3c069867c0dc2eac279bcd778.jpg ACID是什么/存储引掌锦些组件保了这监特性?

  • Atomicity(原子性)
    • Write-Ahead Log(WAL) / Redo Log
  • Consistency(Correctness)(正确性)
    • 依赖于数据库整体
  • Isolation(合理性)
    • Snapshot / 2PL(Phase Lock)
  • Durability(保证数据不丢的性质)
    • Flusher 遵循 Sync 语意

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

  • 屏蔽IO细节提供更好的抽象
  • 提供统计信息与 Predicate Push Down 能力

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

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

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

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

0eeeb8146d7620d8771895a8fe892c8.jpg

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

a40cc86e529f7a4cf9d7a9edb951743.jpg

2.为什么要采用LSMT模型?
  • 在计算机存储乃至整个工程界都在利用Indirection 处理资源的不对称性
  • 存储引擎面对的资源不对称性在不同时期是不同的

HDD 时代

  • 顺序与随机操作性能不对称

由于机械硬盘需要磁盘旋转和机械臂移动来进行读写顺序写吞吐是随机读的25倍。

SSD 时代

  • 顺序写与随机写性能不对称

由于SSD 随机写会给主控带来GC压力顺序写吞吐是随机写的6倍.

小结:

  • HDD 时代,顺序操作远快于随机操作
  • SSD 时代,顺序写操作远快于随机写操作

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

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

a0dbdaaf1a71e1e0154189a6e7b6c54.jpg

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

36e626faa4531687471ef89e2c83147.jpg

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

48725ecdfd8591b518ced5d11567399.jpg

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

9f653e855a0fb1600102889782980a9.jpg

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

fe580dfc9b08950adb1519af7307d60.jpg

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

88ae936813d722995a4609c1d22dd38.jpg

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

74dd5a850a3e4ff70b48398e4b4bbc8.jpg

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

  • CPU 缓存不友好

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

  • CPU 缓存友好

f4c4e2063102d6bf3a7c6f36f8ff059.jpg

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

[1,10]表示这个家引块存储数据的区间在1 10之间。查询2,就是顺着标绿色的块往下

1f4719407917e0d6154d658fef1abcf.jpg

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

6e6abad526a8415a10a640521da2383.jpg

  • Level 策略直接来自于 LevelDB,也是Rocks DB的默认策略。每一个层不允许有SST的Key 区间重合。
3.5 LSMT存储引擎的实现Compact-Tier:

a4ca2b6f10a9c054ab9727c88ce0e49.jpg

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

三、LSMT 模型理论分析。

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

4edd1b6c12a1469f8ff014685a0a23c.jpg

2.LSMT模型算法复杂度分析:
  • T: size ratio,每层LSMT 比上一层大多少,LC )大小为1,则L1大小为T,L2为TA2,以此类推
  • L: level num, LSMT层数B:每个最小的10单位能装载多少条记录
  • M:每个BloomFilter有多少 bits
  • N:每个BloomFilter 生成时用了多少条 Key
  • S:区间查询的记录数量

ed923dc179267d11e2b64122cb40433.jpg LSMT模型算法复杂度分析-Level:

  • Write:

    • 每条记录抵达最底层需要经过L次Compact,,每次 Compact Ln的一个小SST和Ln+1的一个大 SST.
    • 设小 SST的大小为1,那么大 SST的大小则为T,合井开销是1+T,换言之将1单位的Ln的SST推到Ln+1要耗费1+T的 10,单次 Compact 写放大为工。
    • 每条记录的写入成本为1/B次最小单位 1O.
    • O(Write_Level)=LT1/B= T*L/B
  • Point Lookup

对于每条 Key,最多有L个重叠的区间.

每个区间都有 BloomFilter,失效率为en^(-M/N)只有当BloomFilter 失效时才会访问下一层。

O(PointLookup Level)=L*en^(-M/N)

注意,这里不乘1/B 系数的原因是写入可以批量提交拉低成本,但是读取的时候必须对齐到最小读取单元尺寸。

3.LSMT模型算法复杂度分析-Tier:
  • Write:

每条记录抵达最底层前同样要经过L次 Compa ct.每次 Compact Ln 中T 个相同尺寸的SST 放到Ln+1.

设SST 大小为1,那么T个SST Compact的 合并开销是T,换言之将T单位的Ln的SST推到Ln+1要耗费T的10,单次 Compact的写放大为T/T=1.

每条记录的写入成本为1/B次最小单位IO.

O(Write_Tier)=L11/B=L/B

  • Point Lookup: 对于每条Key,有L层.

每层最多有工个重叠区间的SST,对于整个 SST来说有T·L个可能命中的SST,菜上BloomFilter的失效率,e^(-M/N),可得结果。

O(PointLookup_Tier)=LTe^(-M/N)=TLe^(-M/N)

注意,这里不乘1/B系数的原因是写入可以批量 提交拉低成本,但是读取的时候必须对齐到最小读取单元尺寸.

4.LSMT模型算法复杂度分析:
  • Online Quiz
    • Short Range Scan 复杂度是如何推导的?
      • O(Tier)=TLe^(-M/N),但由于点查失效,O(Tier)=T*L,就是重叠区间内SST的个数。
    • Space Amplification 复杂度是如何推导的?
      • O(Tier)=T

a94b8881bc4e4f22a512ce4845ab971.jpg 总结 Tier策略降低了写放大,增加了读放大和空间放大,Level 策略增加了写放大,降低了读和空间放大。

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

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

b5ad76964e3dd9d968f13d44328b035.jpg

2.LSMT存储引擎调优案例-TerarkDB & Abase & ByteGraph:
  • 图存储场景描述:
  • Key size :20B~30B
  • Value size:数十KB 级别
  • 写多读少
  • 收益结论:
  • 延迟大幅度降低,长尾消失,扛住了比 RocksDB 高50%的负载。
3.LSMT存储引擎调优案例-TerarkDB & Flink:
  • 收益结论:
    • 平均CPU开销在 3个作业上降低了 26%~39%
    • 峰值CPU开销在广告作业上有明显的收益,降低了 67%
    • 平均容量开销在3个作业上降低了17%~31.2%
    • 直播业务某集群容量不收缩,TerarkDB的 schedule TTL GC彻底解决了该问题
4,存储引擎最新发展趋势-新硬件:
  • 随着硬件的发展 软件设计也会随着发生改变。近年来,出现了许多新的存储技术,例如SMR HDD Zoned SSD/ OpenChannel SSD, PMem等. 如何在这些新硬件上设计/改进存储引擎是一大研究热点。

e.g. MatrixkV: Reducing Write Stalls and Write Amplification in LSM-tree Based kV Stores with Matrix Container in NVM

这篇论文中的设计将L0整个搬进了 PMem,降低了写放大

5.存储引擎最新发展趋势-新模型:
  • 经典LSMT 模型是比较简单的,有时候不能 立对所有工况,可以提出新的模型来解决问题

e.g. Wisckey: Separating Keys fron Values in SSD-conscious Storage 通过额外增加一个 Value Store 来荐储大 Value 的记录来降低总体写放大。

e.g. REMIX: Efficient Range Query for LSM-trees 通过额外增加一种SST的类型来加速范围查询的速度

6.存储引擎最新发展趋势-新参数/新工况:
  • 已有的模型,在新的或者现有工况下,参数设置的不合理,可以通过更精确的参数设置来提升整体性能。

e.g. The Log-Structured Merge-Bush & the Wacky Continuum

在最后一层使用Level Compaction 之上使用 Tier Compaction,通过在除了最后一层以外的SST 加大 BloomFilter的 bits 数来规 避Tier Compaction 带来的点查劣化。

总结

经过本次课程的学习,我学会了LSMT是通过Append-only Write +择机Compact来维护索引树的结构的。还有LSMT存储引擎的优势:

  • 相对于B+Tree 的优势
    • 顺序写模型对于SSD设备更友好
    • SST不可修改的特性使得其能使用更加紧凑的数据排列和加上压缩
    • 后台延迟Compact 能更好利用CPU多核处理能力, 降低前台请求延迟
  • 相对于HashTable 的优势
    • LSMT 存储引擎是有序索引抽象,HashTable 是无序索引抽象。 无序索引是有序索引的真子集。 LSMT相比于HashTable 更加通用。 HashTable 能处理点查请求,LSMT也能, 但LSMT 能处理 TopK请求,但HashTable 就不行了。 为了避免维护多套存储引擎, 绝大多数数据库都直接采用一套有序的存储引擎而非针对点查和顺序读取分别维护两个引擎。

但对Space Amplification 复杂度是如何推导的?这个问题还是没有搞懂。