写点什么

MatrixOne Layout 设计解读

作者:MatrixOrigin
  • 2023-05-12
    上海
  • 本文字数:4774 字

    阅读完需:约 16 分钟

MatrixOne Layout 设计解读

作者:申江伟 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 结构解析

Extent

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

+------------+----------+-------------+-------------+| Offset(4B) | Size(4B) |  OSize (4B) |  Algo (1B)  |+------------+----------+-------------+-------------+
Offset = IOEntry的起始地址Size = IOEntry存储在对象中的大小oSize = IOEntry压缩前的原始大小Algo = 压缩算法类型
复制代码

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 的元数据信息。

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 idTableID = Table idAccountID = Account idBlockID = 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数据的checksumZoneMap = 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

发布于: 刚刚阅读数: 4
用户头像

MatrixOrigin

关注

还未添加个人签名 2021-12-06 加入

一个以技术创新和用户价值为核心的基础软件技术公司。

评论

发布
暂无评论
MatrixOne Layout 设计解读_分布式数据库_MatrixOrigin_InfoQ写作社区