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

142 阅读7分钟

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

一.LSMT与存储引擎介绍

1.简介:

通过Append-only Write+择机Compact(归并排序)来维护结构的索引树。

image.png 首先,数据会写入WAL,来确保其原子性,后数据会写入Mentable,它是内存中的索引,当mentable写入达到一定阈值时,会进行冻结,生成immutable。所有的修改都会作用于mentable,所以immutable转交给flush线程进行写盘,不用担心并发问题。flush线程在收到immutable后,在进行写盘前会进一步将immutable变成SST(存储在硬盘上的索引),在逻辑上与immutable并无差别,新生成的SST会放置在L0。SST每Compat一次,就会把产物放到下一层(即l1,l2……),多个SST进行Compat后,可以去掉无效和重复条目,生成新的SST。

2.存储引擎:

除保证ACID外,还负责:

  • 屏蔽IO细节提供更好的抽象
  • 提供统计信息与Predicate Push Down能力 存储引擎不掌控IO细节,让操作系统接管,例如使用mmap,会有如下问题:
  • 落盘时机不确定造成的事务不安全
  • IO Stall
  • 错误处理繁琐
  • 无法完全发挥硬件性能

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

1.LSMT与B+Tree的异同

B+Tree:

image.png 插入15,达到5的阈值,就要进行分裂

  • 在B+Tree中,数据插入是原地更新的
  • B+Tree在发生不平衡或者节点容量达到阈值后,必须立即进行分裂来平衡。 LSMT:
  • 数据插入是追加写入
  • 在发生不平衡或者节点容量达到阈值后,由专门的Compat线程进行Compat,是延迟的。

image.png

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

2.为什么要用LSMT模型

HDD时代

  • 顺序与随机操作性能不对称,顺序操作远快于随机操作
  • 由于机械硬盘需要磁盘旋转和机械臂移动来进行读写,顺序写吞吐是随机读的25倍。
    SSD时代:
  • 顺序写于随机写性能不对称,顺序写操作远快于随机写操作
  • 由于SSD随机写会给主控带来GC压力,顺序写吞吐的随机写的6倍 二者的共性是顺序写是一个对设备很友好的操作,LSMT符合这一点,而B+Tree依赖原地更新,导致随机写

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

  • RocksDB写入流程主要有两个优化,批量WAL写入(继承自LevelDB,LevelDB在写时必须先写WAL(原子性依赖于WAL))与并发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

4.LSMT存储引擎的实现:Snapshot&SuperVersion

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

image.png 若所有读者都给SuperVersion的计数加一,读完后再减一,那么这个原子引用计数器就会从未热点,CPU在多核之间同步缓存是有开销的,核越多开销越大。RocksDB为此做了优化:Thread Local SuperVersion Cache。

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

image.png

  • 有Thread Local缓存,读取只需要检查SuperVersion(看其是否过期,若过期,读者需重新刷新,为避免某读者的Thread Local的cache持有该SuperVersion的时间太久,导致资源无法回收,每当有新的SuperVersion生成时,要标记所有读者已经缓存的SuperVersion失效)并标记Thread Local缓存正在使用即可,对CPU缓存友好。

image.png

5.LSMT存储引擎的实现:Get&BoolmFilter

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

image.png

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

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

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

image.png

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

image.png

三.LSMT模型理论分析

1.Cloud-Native LSMT Storage Engine-HBase

  • RocksDB是单机存储引擎,HBase比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生成时用了多少条key
  • S:区间查询的记录数量

image.png

3.LSMT模型算法复杂度分析-Level

Writer

  • 每条记录抵达最底层需要经过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。

image.png Point Lookup

  • 对于每条key,最多有L个重叠的区间。
  • 每个区间都有Bloom Filter,失效率为e^(-M/N),只有当BloomFilter失效才会访问下一层。
  • 这里不乘1/B系数的原因是写入可以批量提交拉低成本,但是读取的时候必须对齐到最小读取单元尺寸。

image.png

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

Writer

  • 每条记录抵达最底层需要经过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。

image.png Point Lookup

  • 对于每条key,有L层。
  • 每层最多有T个重叠区间的SST,对于整个SST来说有T*L个可能命中的SST,乘上BloomFilter的失效率,可得。
  • 这里不乘1/B系数的原因是写入可以批量提交拉低成本,但是读取的时候必须对齐到最小读取单元尺寸。

image.png

5.总结:

  • Short Range Scan复杂度:level:o(L) Tier:O(T*L)
  • Space Amplification:level:o(T) Tier:o(T)
  • Tier策略降低了写放大,增加了读放大和空间放大
  • Level策略增加了写放大,降低了读和空间放大