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

242 阅读9分钟

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

一、笔记内容

1. LSMT 与存储引擎介绍

2. LSMT 存储引擎的优势与实现

3. LSMT 模型理论分析

4. LSMT 存储引擎调优案例与展望

二、LSMT 与存储引擎介绍

LSMT:Log-Structured Merge-Tree。

早期的数据库系统一般都采用B-Tree家族作为索引,例如MySQL。

2000年后诞生的数据库大多采用LSMT索引,例如Google BigTable,HBase,Canssandra等。

1.LSMT

通过Append-only Write +择机Compact来维护结构的索引树。

2.存储引擎

以单机数据库MySQL为例,可以分为:

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

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

image.png

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的异同

B+Tree

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

image.png

相同点:

LSMT与B+Tree可以用统一模型描述。从高层次的数据结构角度来看,二者没有本质的不同,可以互相转化。

image.png

不同点:

  • 工程实践上还是用LSMT 来表示一个 Append-only 和 Lazy Compact 的索引树,B+Tree来表示一个lnplace-Update和Instant Compact的索引树。

  • Append-only 和Lazy Compact这两个特性更符合现代计算机设备的特性。

2.LSMT模型的重要性

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

HDD 时代:

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

SSD时代:

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

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

3.LSMT存储引擎的实现:RocksDB

RocksDB是一款十分流行的开源LSMT存储引擎,最早来自Facebook (Meta),应用于MyRocks,TiDB等数据库,在字节内部也有Abase,ByteKV,ByteNDB,Bytable等用户。

1.LSMT存储引擎的实现:Write

image.png

(1) RocksDB 写入流程主要有两个优化,批量WAL写入(继承自LevelDB)与并发MemTable更新;

(2) RocksDB在真正执行修改之前会先将变更写入WAL,WAL 写成功则写入成功。

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

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

image.png

批量唤醒: 多个写入者会选出一个 Leader ,由这个Leader来一次性写入WAL,避免小lO。

不要求WAL强制落盘(Sync)时,批量提交亦有好处,Leader 可以同时唤醒其余Writer,降低了系统线程调度开销。

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

image.png image.png

2.LSMT存储引擎的实现:Snapshot & SuperVision

image.png

SuperVersion:RocksDB的数据由3部分组成,MemTable/ lmmemTable / SST。持有这三部分数据并且提供快照功能的组件叫做SuperVersion。

MemTable和SST的释放依赖于引用计数。对于读取来说,只要拿着SuperVersion,从MemTable一级—级向下,就能查到记录。拿着SuperVersion 不释放,等于是拿到了快照。

优化:

image.png image.png

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

  • 没有Thread Local缓存时,读取操作要频繁Acquire和 Release SuperVersion,对CPU 缓存不友好。

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

3.LSMT存储引擎的实现:Get & BloomFilter

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

image.png

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

4.LSMT存储引擎的实现:Compact - Level

image.png

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

5.LSMT存储引擎的实现:Compact- Tier

image.png

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

四、LSMT 模型理论分析

1.Cloud-Native LSMT Storage Engine - HBase

image.png

RocksDB是单机存储引擎,SST直接存储于HDFS上。二者在理论存储模型上都是 LSMT。

2.LSMT 模型算法复杂度分析

T: size ratio,每层LSMT比上一层大多少,L0大小为1,则L1大小为T,L2为T^2,以此类推;

L: level num,LSMT层数;

B:每个最小的IO单位能装载多少条记录;

M:每个BloomFilter有多少bits;

N:每个 BloomFilter生成时用了多少条;

KeyS:区间查询的记录数量;

image.png

1.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写放大为工。每条记录的写入成本为1/B次最小单位IO。

O(Write_Level)=L*T*1/B =T*L/B

Point Lookup:对于每条Key,最多有L个重叠的区间。每个区间都有BloomFilter,失效率为e^(-M/N),只有当BloomFilter失效时才会访问下一层。

O(PointLookup_Level) = L*e^(-M/N)

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

2.Tier

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

O(Write_Tier) = L*1*1/B = L/B

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

O(PointLookup_Tier) = L* T*e个(-M/N)= T* L* e^(-M/N)

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

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

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

1.TerarkDB

TerarkDB aka LavaKV是字节跳动内部基于RocksDB深度定制优化的自研LSMT存储引擎,其中完全自研的KV分离功能,上线后取得了巨大的收益。

KV分离受启发于论文 WiscKey: Separating Keys from Values in SSD-consciousStorage,概括起来就是Value 较长的记录的Value单独存储。

image.png

2.TerarkDB & Abase & ByteGraph

图存储场景描述:

  • Key size : 20B ~ 30B

  • Value size:数十KB级别

  • 写多读少

收益结论:延迟大幅度降低,长尾消失,扛住了比 RocksDB高50%的负载。

3.TerarkDB & Flink

在字节内部Flink流处理状态存储场景实测

收益结论:

1.平均CPU开销在3个作业上降低了26%~39%

2.峰值CPU开销在广告作业上有明显的收益,降低了67%

3.平均容量开销在3个作业上降低了17%~31.2%

4.直播业务某集群容量不收缩,TerarkDB的schedule TTL GC彻底解决了该问题

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

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

型: 经典LSMT模型是比较简单的,有时候不能应对所有工况,可以提出新的模型来解决问题。例如:通过额外增加一个Value Store来存储大Value 的记录来降低总体写放大。或通过额外增加一种 SST的类型来加速范围查询的速度

新参数/新工况: 已有的模型,在新的或者现有工况下,参数设置的不合理,可以通过更精确的参数设置来提升整体性能。例如:在最后一层使用Level Compaction,之上使用Tier Compaction,通过在除了最后一层以外的SST加大BloomFilter 的 bits 数来规避Tier Compaction带来的点查劣化。


参考文章:juejin.cn/post/712795…