技术分享 | LSM-Tree 和 OceanBase 分层转储

时间:2020-12-31 00:52:13

作者:金长龙

爱可生测试工程师,负责DMP产品的测试工作

本文来源:原创投稿

* 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


先前在做 OB 存储引擎这块学习的时候,对 OceanBase 的分层转储和 SSTable这块有些细节就懵懵的,比如L0层的 mini SSTable 的每次生成是否就计入转储次数,L0层到L1层转储的时机以及和 minor_compact_trigger 之间的关系等。今天就这部分内容做个更细致的探究,试图更深入的理解 OceanBase 的分层转储。

一、LSM-Tree

首先来看一下 LSM-Tree(全称是 Log-Structured Merge Tree),当下许多较新的数据库都会选择 LSM-Tree 作为存储结构,比如 TiDB 、Cassandra 、OceanBase 等。LSM-Tree 的优势是顺序写,提升了整体写入性能。
技术分享 | LSM-Tree 和 OceanBase 分层转储
LSM-Tree 大致可以分为两部分:
  • Memt
    able
    : 常驻内存的 KV 查找树  +
    无序的 WAL 文件
  • SS
    Table (Sorted Strin
    g Table)
    : 一组存储在磁盘的不可变文件,存储有序的键值对

写入流程

1、同步写 Memtable
先将数据写入 WAL 文件,然后修改内存中的 AVL,因此最优情况下,每次写操作只有一次磁盘 I/O。
删除操作并不会直接删除磁盘中的内容,而是将删除标记(tombstone)写入 Memtable 。当 Memtable 增大到一定程度后,则会转换为 Immutable Memtable 并产生一个新的 Memtable 接受写操作。
2、异步写 SSTable
后台会启动一个合并线程,当 Immutable Memtable 达到一定数量,合并线程会将其写入磁盘(Flush),生成 Level 0 的 SSTable 文件。
当 Level N 的 SSTable 文件数量到达阈值之后,会进行合并压缩(Compaction)操作,在 Level N+1 生成新的 SSTable 文件。
SSTable 分为多层,单个文件的大小通常是上一层的 10 倍,每层可以同时包含多个 sst file,每个文件由多个 block 组成,其大小约为 32K,是磁盘 IO 的基本单位。
第 Level i (i > 0) 层的 SSTable 满足:
  • 第 i 层所有文件均由 i - 1 层的 SSTable 合并排序而来,可以通过设定阈值(文件个数...)来控制合并的行为

  • 文件之间是有序的,且每个文件的 key 集合不会与其他文件有交集(Level 0 的 SSTable 除外)

Compaction 策略

常用的 Compaction 策略有 Classic Leveled、Tiered、Tiered & Leveled 、FIFO 等,简单介绍下前3种。

1、Classic Leveled
Classic Leveled 模式下每一层都是独立的"Sorted Run" ,代表是按 Key 排序且同层 sst file 之间的 Key 值没有重合,数量大小是逐层增大。相邻的两层 sst file 比称之为fanout(扇出),每次做 Compaction 的条件是Ln层大小达到了阈值,将Ln层数据与Ln+1层数据进行合并。由于每次做 Compaction 都将Ln层数据写入到Ln+1中,写放大情况会比较严重, 比如L1 ,L2 两层 fanout 是10,那么L1层写满后与L2层做排序合并,重写生成新的L2层,那么写放大最坏情况下等于 fanout
2、Tiered
Tiered 模式与 Classic Leveled 的区别在于每一层的sst file之间Key有重合的,每层有多个"Sorted Run",每次做 Compaction 都是同层先做合并生成一个新的 sst file 写入到下一层中,这里与 Leveled 最重要区别是写入到下一层后不再需要排序合并、重写,因为 Tiered 每层存在多个"Sorted Run",那么写放大最坏情况下为1。但是相比于 Leveled ,会有读放大和空间放大会比较严重。
3、Tiered & Leveled

Tiered & Leveled 模式是指对于层级较小的 Level ,数据量比较小,写入的数据较新,被更新的可能性比较大,使用 Size-Tiered 模式减少写放大问题;对于层级较大的 Level , SSTable 的数据量较大,数据比较旧不太容易被更新,使用 Leveled 模式减少空间放大问题。

二、OceanBase 的分层转储

OceanBase 数据库的存储引擎就是基于 LSM-Tree 架构的设计,也是划分为内存中的MemTable 和磁盘上的 SSTable 。OceanBase 将磁盘上的 SSTable 划分为三层,使用的是 Tiered & Leveled 的 Compaction 策略,在 L0 层使用 Tiered 模式,在 L1 层、L2 层使用 Leveled 模式。
技术分享 | LSM-Tree 和 OceanBase 分层转储
OceanBase 中的 Compaction 分为三种类型:Mini Compaction 、Minor Compaction 、Major Compaction 。其中 Major Compaction 指的是大合并,我们先不谈,这里只说一下Mini Compaction和Minor Compaction

Mini Compaction (转储)

技术分享 | LSM-Tree 和 OceanBase 分层转储
Mini Compaction 是一种 Tiered 类型的 Compaction,核心就是释放内存和数据日志,内存中的 Frozen MemTable 通过 Mini Compaction 变成磁盘上的 Mini SSTable。
Mini Compaction 在OceanBase设计里代表的就是一次转储,对应的类型是 MINI_MERGE

Minor Compaction

随着用户数据的写入,Mini SSTable 的数量会逐渐增多,在查询时需要访问的 SSTable 数量会增多,会影响查询的性能。Minor Compaction 就是将多个 Mini SSTable 合成一个,主要目的是减少 SSTable 的数量,减少读放大问题。当 Mini SSTable 的数量超过阈值时,后台会自动触发 Minor Compaction。
Minor Compaction 细分为两类:
1、L0 -> L0
Tiered 类型的 Compaction,将若干个 Mini SSTable 合成一个 Mini SSTable,放置于 L0 层。对应的类型是 MINI_MINOR_MERGE
技术分享 | LSM-Tree 和 OceanBase 分层转储
2、L0 - > L1

Leveled 类型的 Compaction,将若干个 Mini SSTable 与 Minor SSTable 合成一个新的 Minor SSTable,放置于 L1 层。对应的类型是 MINOR_MERGE

技术分享 | LSM-Tree 和 OceanBase 分层转储

实验(使用社区版 OceanBase 4.0.0.0)

测试创建的租户 ob_bench ,内存2G。几个主要参数设置为

memstore_limit_percentage = 50
freeze_trigger_percentage = 20
minor_compact_trigger = 2
_minor_compaction_amplification_factor = 25
major_compact_trigger = 9999  (我们本次实验仅是想>探索L0、L1级的Compaction,不希望触发大合并,所以该参数设置一个极大值)

实验一:在持续数据流的情况下,观测L0, L1层转储的时机

1、创建测试库 sysbench ,用 sysbench 工具创建1张表sbtest1、数据100W。

租户每触发一次转储 memtable dump flush 的数据必然是包含许多表的,我这里只创建1张业务表,仅是希望后续测试时业务变更相对集中

sysbench /usr/share/sysbench/oltp_insert.lua --mysql-host=172.30.134.1 --mysql-db=sysbench  --mysql-port=2881 --mysql-user=root@ob_bench  --tables=1 --table_size=1000000 --report-interval=10 --db-driver=mysql --skip-trx=on --db-ps-mode=disable --create-secondary=off --mysql-ignore-errors=6002,6004,4012,2013,4016 --threads=10 --time=600  prepare
技术分享 | LSM-Tree 和 OceanBase 分层转储

先通过视图 DBA_OB_TABLE_LOCATIONS 找到 sbtest1 对应的 TABLET_ID ,然后通过 GV$OB_TABLET_COMPACTION_HISTORY 查询到在创建100W数据过程中,已经触发了4次 MINI_MERGE 和1次 MINI_MINOR_MERGE

技术分享 | LSM-Tree 和 OceanBase 分层转储

3、对 sbtest1 持续的写数据,观测 sbtest1 表级的转储情况

sysbench /usr/share/sysbench/oltp_insert.lua --mysql-host=172.30.134.1 --mysql-db=sysbench  --mysql-port=2881 --mysql-user=root@ob_bench  --tables=1 --table_size=1000000 --report-interval=10 --db-driver=mysql --skip-trx=on --db-ps-mode=disable --create-secondary=off --mysql-ignore-errors=6002,6004,4012,2013,4016 --threads=10 --time=600  run
技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
测试总结:
官方对于参数 minor_compact_trigger 的解释:“minor_compact_trigger 用于控制分层转储触发向下一层下压的阈值。当该层的 Mini SSTable 总数达到设定的阈值时,所有 SSTable 都会被下压到下一层,组成新的 Minor SSTable 。”  

如上测试时我们设置的 minor_compact_trigger = 2 ,按理解在每两次触发 MINI_MERGE 之后,就会触发一次 MINOR_MERGE ,把L0层的 SSTable 下压到L1层。实际测试下来发现未必如此,当达到 minor_compact_trigger 的阈值后,必然会触发 Minor Compaction ,但它可能是L0层上的 MINI_MINOR_MERGE(同层数据合并),也可能是L0->L1层的 MINOR_MERGE(数据下压到下一层)。但是具体什么情况下,触发哪种 Minor Compaction ,在官方文档只是介绍会受隐藏参数_minor_compaction_amplification_factor控制,但是具体如何影响的 也并没有给到相应的观测手法。

附:官网对参数 _minor_compaction_amplification_factor 的解释:“_minor_compaction_amplification_factor 控制 L0 层内部多个 Mini SSTable 转储的时机,默认为 25。当所有 Mini SSTable 的总行数达到 Minor SSTable 的写放大系数比例后,才会触发 L1 层转储,否则触发 L0 层转储。当 L1 层不存在 Minor SSTable  时,所有 Mini SSTable 行数到指定阈值(由 minor_compact_trigger 控制)后才会触发 L1 层转储。”

实验二:alter system minor freeze 是否真的在L1层做一个 MINOR_MERGE 类型的 compaction ?

1、同实验一的参数配置,且minor_compact_trigger = 2

先对sbtest1表记录做一次update,然后手动执行 alter system minor freeze ;(因为我们实验观测的是指定表的 merge 情况,所以在 minor freeze 之前要做一次 update 操作,主要是保证 memtable 中有对该表操作的记录)

技术分享 | LSM-Tree 和 OceanBase 分层转储
如上我们看到,alter system minor freeze 之后只是做了一次 MINI_MERGE ,并没有到L1层。

我们再执行一次 alter system minor freeze ;

技术分享 | LSM-Tree 和 OceanBase 分层转储
这一次我们发现在做了一次 MINI_MERGE 之后,触发了 MINI_MINOR_MERGE 。
我们可以继续这样做下去,最终我们发现 alter system minor freeze 实际上做的是 MINI_MERGE ,在 MINI_MERGE 之后具体是否会触发 MINI_MINOR_MERGE 或  MINOR_MERGE ,还是会受实验一里面所提到的参数 minor_compact_trigger 和  _minor_compaction_amplification_factor 的控制。
2、同实验一的参数配置,但设置 minor_compact_trigger = 0

同样是多次执行 alter system minor freeze ,每次执行后观察 merge 情况。

技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
技术分享 | LSM-Tree 和 OceanBase 分层转储
可以看到在 minor_compact_trigger = 0 时,当内存中的 memtable dump flush 到L0层后,会立刻下压到L1层, 这点同官方文档中的解释是一致的。
测试总结:

通过实验二的测试我们发现,alter system minor freeze 真正做的是形成一个 mini sstable (MINI_MERGE) ,在 MINI_MERGE 之后是否还会触发其他的 merge ,同样是受参数 minor_compact_trigger 和 _minor_compaction_amplification_factor 的控制。并且指令中的 minor freeze 实际上并不是特别准确,因为看到 minor 总会让人想到L1层,如果改成 mini freeze 会更合适一些。

参考资料
https://www.cnblogs.com/buttercup/p/12991585.html
https://www.oceanbase.com/docs/community-developer-advance-0000000000634015

本文关键字:#OceanBase# #分层转储#