图文解读MatrixDB自研存储引擎

存储引擎是数据库核心组件之一,本文将向读者介绍MatrixDB支持的存储引擎及其内部存储结构和对比。

数据存储方式

在介绍存储引擎之前,先介绍一下数据存储常用的几种方式:

  1. 行存
  2. 列存
  3. 行列混存

行存,如下图所示,数据按行存储。同一行中不同的列存储在一起: 行存

列存,与行存相对应,数据按列存储。不同行中相同的列存储在一起: 列存

行列混存,兼具了行存和列存的特点,按照行分了若干组,每个组中按列存储: 行列混存

MatrixDB存储引擎

MatrixDB提供了Heap和Mars两种存储引擎,分别支持行存和行列混存两种存储方式。

Heap

Heap存储引擎是行存方式,是MatrixDB建表时使用的默认存储引擎。

如下图所示,每个Heap表对应一个数据文件(上限1G,超过会增加),数据文件由Block构成,每个Block内存储了表中的多个行: Heap

Heap表数据存储并无顺序,按照插入先后顺序存储。所以,对Heap表基于键值快速检索要额外建立索引,最常用的就是BTree索引。

如下图所示,BTree索引是一颗高度平衡树,数据索引存储在叶子节点中,指向数据所在行: BTree

Mars

Mars存储引擎是MatrixDB自研的存储引擎,是行列混存。

Mars引擎包含两个重要的概念

  1. Row Group
  2. Footer

Row Group

如图所示,Mars引擎中数据是按Row Group组织起来的,每个Row Group包含了一组有序存储的行。Row Group内部按列分页存储: Mars

那么一个Row Group中要存储多少行呢?这取决于建表时定义的time bucket。作为时序数据库,所有数据都是来自设备,会包含设备id(tag_id)和时间戳。

所以,当新数据时间戳超过time bucket阈值或设备id变化的时候,就会创建新的Row Group。Row Group内部数据有序存放。

行到组

Footer

Footer存储了Row Group的元数据,如:行数、累计和、最大值、最小值,相当于Row Group的稀疏索引。在MatrixDB中每个Row Group对应一个Footer,存储在独立的文件中: Footer到组

Mars引擎查询执行优化

从前面了解到Mars引擎的数据存储有如下特征:

  1. 数据是按照设备id和时间戳有序存储
  2. 每个Row Group内按列存储
  3. 每个Row Group在Footer中都包含COUNT,SUM,MIN,MAX聚集结果

基于如上特征,可以对查询执行做如下优化:

列裁剪

如果查询中只指定了部分列,则可以使用列裁剪,即在数据扫描时跳过不需要的页。

如下图所示,当只查询Column1的时候,做Scan只读取Row Group中Column1的相关页即可,大大减少了IO操作,这也是列存的好处。 列裁剪

扫描裁剪

因为每个Row Group的MIN,MAX都已知晓,所以在做条件扫描的时候就可以根据扫描条件和每个Row Group的最大值、最小值来决定是否需要进行裁剪。 扫描裁剪

如下条件可以利用裁剪:

等值条件

SQL样例:SELECT * FROM table WHERE c1 = v0; 等值条件SQL,如果目标值在最小值和最大值范围内,则对Row Group进行扫描,否则跳过: 等值扫描裁剪

大于或大于等于条件

SQL样例:SELECT * FROM table WHERE c1 >= v0; 当比较值小于等于最大值max的时候,需要进行扫描;否则跳过: 大于等于扫描裁剪

小于或小于等于条件

SQL样例:SELECT * FROM table WHERE c1 <= v0; 当比较值大于等于最小值min的时候,需要进行扫描;否则跳过: 小于等于扫描裁剪

注意:并不是所有的条件都能利用扫描裁剪,比如不等于条件就无法使用。

定制聚集扫描

由于每个Row Group对应的元信息Footer中已经包含了MIN,MAX,SUM,COUNT聚集结果,所以对于整个Row Group都满足扫描条件的聚集查询,则可以直接使用Footer中的聚集结果,而跳过对原始数据的扫描,从而提高性能: 定制聚集扫描

如下图,SQL样例:SELECT COUNT(*) FROM table,对比新旧两种聚集计划可以看出,新的计划省掉了Partial Aggregate和Seq Scan节点,被替换成直接从Footer取结果的Custom Scan: 定制聚集扫描Plan

定制排序扫描

因为时序数据在灌入Mars表的时候是按照tag_id和时间戳排序的。所以,如果查询也是ORDER BY tag_id, time的话,则不需要对扫描结果做额外排序,直接使用即可。

SQL样例:SELECT * FROM table ORDER BY tag_id, time,执行计划前后对比如下图: 定制排序扫描Plan

时序场景存储引擎搭配方案

在时序场景中,既要做海量数据接入,又要做高效查询,所以Heap和Mars两种引擎要搭配使用。推荐做法是:使用Heap接入实时热数据,当热数据时间窗口过后,将Heap数据排序并导入到Mars引擎。具体使用方法请参考冷热分级存储 冷热分级存储

该方案结合了两种引擎的特征,优势如下:

  1. Heap可以建立唯一索引,方便热数据去重
  2. 如果热数据有错误,Heap可以直接做更新和删除
  3. 将Heap数据排序后导入Mars可以有效解决设备发送数据乱序问题
  4. 冷数据不需要更新,存储在Mars中查询更高效

注意:数据从Heap表导入Mars表的时候要按照设备id和时间戳做排序。

总结

对比MatrixDB中两种引擎可以看出:

  1. Heap表按行集中存储,相同的行存在一个Block中,数据无序存储,无预聚集。按键值检索需要额外建立索引。
  2. Mars表按列集中存储,相同的列存在一个Page里,数据有序存储。通过Footer来做稀疏索引和预聚集,存储空间更小,基于设备id和时间戳的检索速度快。

所以,Mars引擎更适合基于时间戳的分析查询,因为:

  1. 数据分析通常针对某个指标,即某个列,并不需要所有列的信息。按列存储可以在物理上只读取需要的列,减少不必要IO操作
  2. 相同的列因为类型相同,更容易做压缩
  3. Footer中包含了Row Group的聚集信息,在做更大范围的聚集操作时则可以减少很多计算量;做条件扫描也可以做裁剪
  4. 数据有序存储,对于需要按照排序键排序的查询,可以降低成本