关系模型存储方案

前面介绍了MatrixDB采用的是关系模型存储方案。下面将详细介绍在关系模型下如何设计时序表。

1. 时序数据组成

时序数据由设备周期产生,所以至少包含如下信息:

  1. 设备标识
  2. 采集指标
  3. 时间戳

其中,时间戳是一个比较统一和规范的数据类型,如:

2021-11-16 10:32:29

相比而言设备标识和指标数据则较为复杂

1.1 设备标识

设备标识用来标识并定位到一个具体的设备,包括如下信息:

  1. 设备类别:哪种类别的设备,监视器、传感器、测量仪等
  2. 设备型号:对于某个类别的设备来说,又分很多型号,如:传感器1.0、传感器2.0等
  3. 设备编号:同一种类型和型号的类别又有很多,设备编号是每个设备独一无二的标识,如手机的序列号
  4. 设备标签:为了便于对设备进行统计分类,还可以额外给设备添加标签,如:城市标签、区域标签、购买渠道标签等

1.2 采集指标

采集指标是设备采集到的数据,不同类别的设备指标数据差别较大。比如监视器采集到的是图片和视频;传感器采集到的是温度、湿度、风速等。从广义上讲,时序指标数据不仅仅来自于硬件设备,也包括软件产生的时序数据,如服务器生成的日志。所以,指标数据类型、形式、生成周期差别都非常大。

2. 表模型设计

对于关系模型来说需要有固定的模式,所以要先建模。

2.1 建哪些表

那么建哪些表呢?先从设备标识来看,设备从大到小分为类别、型号、编号、城市、区域等属性,是每个类别建一张表、还是每个型号建一张表、还是每个设备单独一张表呢、还是所有设备都放一张表里呢?

建议是相同类别的数据建一张表。因为设备量并不小,并且会增长,不可能为每个设备单独建表,这样无论是运维成本还是元数据的管理成本都太高了。也不可能所有类别设备都存一张表里,因为不同类别设备采集的指标差别也很大,另外一张表太大数据太杂也不好做统计分析和维护。

所以,按类别划分后,可能包含如下表:

  1. 传感器表
  2. 测量仪表
  3. ......

2.2 设备标识去冗余

确定了哪些表后,下面要考虑每张表要如何设计。既然设备类别已经在建表时确定了,那么剩下的时序信息则如下所示:

气象传感器采集表

设备标识 采集指标 时间戳
型号1,编号0001,北京市,怀柔区 温度:29.2℃,风力:3.2km/h,湿度:55% 2021-11-16 13:57:13
型号2,编号0010,上海市,崇明区 温度:21.5℃,风力:8.5km/h,湿度:35% 2021-11-16 13:57:13

对于设备标识来说,可能要占用比较大的存储空间,因为包含的各种属性信息比较多。且除了设备编号唯一外,其他的诸如型号、城市、区域等属性信息存在大量重复,没必要为每条采集信息都完全存储下来。这里可以使用关系数据库设计的原则,额外创建一张设备标识表,并生成ID。然后,采集表和设备标识表通过ID来关联,如下:

设备标识表

ID 型号 编号 城市 区域
1 型号1 0001 北京市 怀柔区
2 型号2 0010 上海市 崇明区
......

气象传感器采集表

设备ID 采集指标 时间戳
1 温度:29.2℃,风力:3.2km/h,湿度:55% 2021-11-16 13:57:13
2 温度:21.5℃,风力:8.5km/h,湿度:35% 2021-11-16 13:57:13
......

这样划分后,采集表中的设备标识信息存储空间大大减少,仅为一个整型。设备标识表的数据量为设备总量,设备数量相比于采集指标来说相对固定。

2.3 指标数据存储方案

设备标识模型解决了,下面看采集指标如何建模。采集指标按照存储方式可分为窄表和宽表:

窄表

窄表方案每行只存一个指标数据,如:

设备ID 指标名 指标值 时间戳
1 温度 29.2℃ 2021-11-16 13:57:13
1 风力 3.2km/h 2021-11-16 13:57:13
1 湿度 55% 2021-11-16 13:57:13
2 温度 21.5℃ 2021-11-16 13:57:13
2 风力 8.5km/h 2021-11-16 13:57:13
2 湿度 35% 2021-11-16 13:57:13
......

这种存储方案的优势是:

  1. 灵活,新增指标值可直接扩展
  2. 因为没有固定的模式,不存在某个列因为无值而为NULL

劣势是:

  1. 浪费存储空间:每个指标一行,设备ID,时间戳都是冗余的,指标名也额外占用存储空间
  2. 对分析不友好:因为不同采集指标值不是完全独立的,分开存储不利于一起分析

宽表

与窄表相对应的是宽表,宽表中,一行数据把设备采集的所有指标都存下来,如:

设备ID 温度 风力 湿度 时间戳
1 29.2℃ 3.2km/h 55% 2021-11-16 13:57:13
2 21.5℃ 8.5km/h 35% 2021-11-16 13:57:13
......

宽表的优势是:

  1. 相同设备相同时间的采集数据存在一起,便于统一获取统计分析
  2. 设备ID和时间戳信息无冗余

宽表的劣势是:

  1. 不灵活,增加新指标时需要改表结构
  2. 对于无指标值的列存在NULL,有额外的存储开销

综上,宽表和窄表的本质区别就是一行存储一个采集指标还是全部采集指标。在生产环境中,不建议使用窄表方案。那宽表带来的问题又如何解决呢?下面我们来分析一下宽表会遇到哪些问题,及解决方案。

宽表的挑战

  1. 模式固定:宽表每次增加新指标要ALTER TABLE ADD COLUMN,在表非常大的时候该操作不仅慢而且会阻塞读写操作,影响业务。
  2. 指标集合差异与缺失:虽然相同类别的设备放到了一张表里,但是不同型号的设备指标集合还是会有差异,比如早期采购的设备可能采集不了湿度,这就意味着这种类型的设备湿度值为NULL。另外,设备在采集与回传数据时也会存在丢失,比如某个指标没采集到,也会导致指标值为NULL。还有一种情况是数据指标异频采集,也会产生NULL。NULL数据并不是完全不占存储空间,根据Heap表的内部存储结构来看,一旦行中有NULL值,则会额外增加[列数/8]个字节的存储空间。
  3. 列数限制:MatrixDB的Heap表继承自PostgreSQL,每个表的列数不能超过1600个,某些采集指标达到几千个的场景,无法直接胜任。

针对如上挑战,MatrixDB开发了mxkv高性能KV存储类型,可以任意扩展列数,和json类型类似,性能远远高于json。

使用mxkv后,表可以这样设计:

设备ID 指标 时间戳
1 {"temperature":29.2,"wind":3.2,"humidity":55} 2021-11-16 13:57:13
2 {"temperature":21.5,"wind":8.5,"humidity":35} 2021-11-16 13:57:13
......

但是mxkv也不是万金油,使用mxkv后,虽然在Heap表中读写性能不差,但有如下缺陷:

  1. 不利于列压缩。因为冷数据最终要放到mars表中做压缩的,mars是行列混存,而mxkv内部还是行存
  2. 目前mxkv暂时不支持merge操作,无法通过一次操作在更新时将新数据merge进去,而是需要先把旧数据取出来手动合并到一起。

如上两点问题会在Mars3上线后解决。

3. 总结

  1. MatrixDB是关系型数据库,需要使用关系模型建立数据模型,并且建议使用宽表
  2. 设备信息要额外建立一张设备表来存储,并通过ID与指标表关联以减少冗余
  3. mxkv类型解决了关系表模式固定、列数限制与NULL的问题,但是不利于压缩存储(Mars3上线后解决)