MatrixOne Layout 设计解读

原创
05/12 19:15
阅读数 87

作者:申江伟 MatrixOne 研发工程师

MatrixOne从0.5设计开始就已经确定采用列存结构来存储数据集,原因如下:

  1. 很容易对AP优化;
  2. 通过引入Column Family的概念可以对负载灵活适配。假如所有列都是一个Column Family,也就是所有的列数据保存在一起,这就跟数据库的HEAP文件非常类似,可以表现出行存类似的行为,典型的OLTP数据库如PostgreSQL就是基于HEAP来做的存储引擎。假如每个列都是独立的Column Family,也就是每一列都独立存放,那么就是典型的列存。通过定义Column Family,用户可以方便地在行存和列存之间切换,这只需要在DDL表定义中指定即可。

Part 1 Layout需要满足的条件及解决的问题

设计一种Layout,首先要提出一个疑问。

我们到底需要满足什么功能和需求?

对于MatrixOne来说,需要存储的数据类型有很多种,比如:数据库表数据、元数据、数据库业务trace log、查询结果的cache... ... 

功能1:支持存储MatrixOne所有的业务类型数据。

MatrixOne需对接S3或共享对象存储,每一个对象需要存储指定行数或Size的存储集,所以一个对象会存储多个数据单元,对于每一个数据单元我们称之为Block。这些Block需要高效读取和管理,比如一个查询需要读取哪些Block,首先需要拿到Block的元数据并分析,然后再根据这些Block的元数据得到真正数据的存放地址并读取。

功能2:便捷高效的元数据。

当MatrixOne运行一段时间,客户需求不断增加,这时不得不修改当前的Layout。

功能3:支持数据的版本兼容和控制。

支持数据分析工具和添加Scrub任务。

功能4:支持从数据对象文件中重建MatrixOne的表结构。

根据上诉几个问题,我们设计了现在的Layout,它由Header、数据区、索引区、元数据区和Footer组成。这些区域的作用如下:

Header :记录了当前Layout的版本、元数据区的位置等信息。

数据区 :存储了数据对象的数据信息。

索引区 :存储了数据对象的索引信息。

元数据区 :存储了数据对象的元数据信息,这些元数据包括数据对象的类型、数据对象的大小、行数、列数、压缩算法等信息。

Footer :可以看作是一个Header的镜像。


Part 2 结构解析

1 Extent

在MatrixOne中,Extent负责记录一个IOEntry的位置信息。

+------------+----------+-------------+-------------+
| Offset(4B) | Size(4B) |  OSize (4B) |  Algo (1B)  |
+------------+----------+-------------+-------------+

Offset = IOEntry的起始地址
Size = IOEntry存储在对象中的大小
oSize = IOEntry压缩前的原始大小
Algo = 压缩算法类型

2 ObjectMeta

为什么我们先介绍ObjectMeta而不是Header,因为MatrixOne的查询业务是从ObjectMeta开始的。 MatrixOne向S3写入数据成功后,会返回一个记录ObjectMeta位置的Extent,并保存到Catalog中。 当MatrixOne执行查询操作需要读取数据时,可以通过这个Extent拿到ObjectMeta,从而拿到Block的真实数据。

ObjectMeta由多个BlockMeta、一个MetaHeader、一个BlockIndex组成。 MetaHeader和BlockMeta结构一致,用来记录整个Object的全局信息,比如dbID,TableID,ObjectName和Object全局数据的的ZoneMap、Ndv、nullCut等等。

BlockIndex记录后面每一个BlockMeta的地址。ObjectMeta在MatrixOne系统中使用非常频繁, 虽然每一个BlockMeta的地址可以遍历算出来,但是为了节省开销和提高性能,还是记录了BlockIndex。

BlockMeta记录了每一个Block的元数据信息。

3 BlockMeta & MetaHeader

BlockMeta由一个Header和多个ColumnMeta组成。

Header记录了BlockID、Block的Rows、Column Count等信息。

ColumnMeta记录了每一列的数据地址、Null Count(当前列Null值的数量)、Ndv(当前列有多少不同的值)等信息。

>>> Block Meta Header

+----------------------------------------------------------------------------------------------------+
|                                              Header                                                |
+----------+------------+-------------+-------------+------------+--------------+--------------------+
| DBID(8B) | TableID(8B)|AccountID(4B)|BlockID(20B) |  Rows(4B)  | ColumnCnt(2B)| BloomFilter(13B)   |
+----------+------------+-------------+-------------+------------+--------------+--------------------+
|                                            Reserved(37B)                                           |
+----------------------------------------------------------------------------------------------------+
|                                             ColumnMeta                                             |
+----------------------------------------------------------------------------------------------------+
|                                             ColumnMeta                                             |
+----------------------------------------------------------------------------------------------------+
|                                             ColumnMeta                                             |
+----------------------------------------------------------------------------------------------------+
|                                             ..........                                             |
+----------------------------------------------------------------------------------------------------+

DBID = Database id
TableID = Table id
AccountID = Account id
BlockID = Block id。
Rows = MetaHeader中为对象的行数,BlockMeta中为当前Block的行数
ColumnCnt = 在对象或Block中有多少列
BloomFilter = 存储BloomFilter区域的地址,只在MetaHeader中有效

>>> Column Meta

+--------------------------------------------------------------------------------+
|                                    ColumnMeta                                  |
+--------+---------+-------+-----------+---------------+-----------+-------------+
|Idx(2B) |Type(1B) |Ndv(4B)|NullCnt(4B)|DataExtent(13B)|Chksum(4B) |ZoneMap(64B) |   
+--------+---------+-------+-----------+---------------+-----------+-------------+
|                                   Reserved(32B)                                |
+--------------------------------------------------------------------------------+

Idx = Column的序号
Ndv = Column中有多少不同的值
NullCnt = Column中有多少Null值
DataExtent = Column数据的位置
Chksum = Column数据的checksum
ZoneMap = Column的ZoneMap,大小固定为64B

>>> Header 

Header和Footer记录信息一致,只不过位置不同。

+---------+------------+---------------+----------+
|Magic(8B)| Version(2B)|MetaExtent(13B)|Chksum(4B)|
+---------+------------+---------------+----------+

Magic = Engine identity (0x0xFFFFFFFF)
Version = 对象文件的版本号
MetaExtent = ObjectMeta的位置信息
Chksum = ObjectMeta的checksum

>>> Footer 

+----------+----------------+-----------+----------+
|Chksum(4B)| MetaExtent(13B)|Version(2B)| Magic(8B)|
+----------+----------------+-----------+----------+

Part 3 通过Extent读数据

在介绍ObjectMeta时,我们知道,MatrixOne向S3写入数据成功后, 会返回一个记录ObjectMeta位置的Extent。这里时候我们会通过Extent来执行读数据的操作。

首先,通过Extent中的地址,向S3请求读一个IO Entry,并且放入MatrixOne的cache中。 这个IO Entry就是ObjectMeta的全部内容。通过位移计算可以拿到BlockIndex,根据BlockIndex记录的Extent可以得到被读的 BlockMeta,到此为止我们元数据操作已经结束,剩下就是向S3请求读一个个Column Data了。

   +-----------+
      |   Extent  |
      +-----------+                   
            |
            |
+-------------------------+            
|        IO Entry         |
+-------------------------+
|       ObjectMeta        |
+-------------------------+
            |
            |
+---------------------------------------------------------------------
|                            BlockIndex                              |
+--------+------------+------------+------------+-----------+--------+
| Count  | <Extent-1> | <Extent-2> | <Extent-3> | Extent-4> | ...... |
+--------+------------+------------+------------+-----------+--------+
               |                                       |
               |                                       |
+------------------------------------------------+     |
|             Block1(BlockMeta)                  |     |
+--------------+--------------+--------------+---+     |
|<ColumnMeta-1>|<ColumnMeta-2>|<ColumnMeta-3>|...|     |
+--------------+--------------+--------------+---+     |   
        |               |               |              | 
        |               |               |       +------------------+
  +----------+    +----------+    +----------+  | Block4(BlockMeta)|
  | IO Entry |    | IO Entry |    | IO Entry |  +------------------+
  +----------+    +----------+    +----------+  | <ColumnMeta>...  |
  |ColumnData|    |ColumnData|    |ColumnData|  +------------------+
  +----------+    +----------+    +----------+          |
                                                        |
                                                +------------------+
                                                |    IO Entry...   |
                                            +------------------+       

Part 4 版本兼容

>>> IOEntry 

IOEntry表示一个IO单元,具体对应在Layout中就是:ObjectMeta、每一个Column的数据、BloomFilter区还有Header和Footer。 除了Header和Footer,我们需要在每一个IOEntry头添加两个flag:Type&Version。

每一个结构或模块需要实现Encode/Decode函数,然后注册到MatrixOne中,MatrixOne读出IOEntry后会根据这两个flag来选择对应的代码。

const (
  IOET_ObjectMeta_V1  = 1
  IOET_ColumnData_V1  = 1
  IOET_BloomFilter_V1 = 1
  ...

  IOET_ObjectMeta_CurrVer  = IOET_ObjectMeta_V1
  IOET_ColumnData_CurrVer  = IOET_ColumnData_V1
  IOET_BloomFilter_CurrVer = IOET_BloomFilter_V1
)

const (
        IOET_Empty   = 0
        IOET_ObjMeta = 1
        IOET_ColData = 2
        IOET_BF      = 3
        ...
)

以ObjectMeta为例。我们需注册V1的Encode/Decode函数代码,设置IOET_ObjectMeta_CurrVer为V1,并写入数据。

const (
    IOET_ObjectMeta_V1  = 1
    IOET_ObjectMeta_V2  = 2
    ...

    IOET_ObjectMeta_CurrVer  = IOET_ObjectMeta_V2
    ...
)

func EncodeObjectMetaV2(meta *ObjectMeta) []byte {
  ...
}
func DecodeObjectMetaV2(buf []byte) *ObjectMeta {
        ...
}
RegisterIOEnrtyCodec(IOET_ObjMeta,IOET_ObjectMeta_V2,EncodeObjectMetaV2,DecodeObjectMetaV2)
ObjectMeta.Write(IOET_ObjMeta, IOET_ObjectMeta_CurrVer)

MatrixOrigin 官网: 新一代超融合异构开源数据库-矩阵起源(深圳)信息科技有限公司 MatrixOne

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部