数据建模
任何一款数据库,数据都遵循某种模型,如关系模型、KV模型等。超融合时序数据库MatrixDB,以关系模型为基础,增加了可扩展数据类型,使其既能方便做关系运算,同时又不受关系模型固定的限制,既高效又灵活。
本课程教学视频请参考MatrixDB数据建模与时空分布模型
1. 数据范式
要接入时序数据,首先要知道时序数据包含如下信息:
- 设备信息
- 指标信息
所以,在做数据接入时,MatrixDB需要如下数据表:
- 设备信息表,即tags表
- 设备指标表
其中,设备信息表包括设备ID和其他属性,如名称、位置等。 指标表来存设备ID和所有指标。
之所以要把两个表分开而不是存在一起,主要原因是:
- 设备属性字段值相对固定,不像指标实时变化,没必要为每个指标单独存一份,这样设计更符合数据库设计范式
- 时序数据接入量大,把设备属性存储成ID来标识,可以减少发送数据时消耗的网络带宽
- 设备名称等属性字段通常为变长字符串,存储在指标表中不利于后面的压缩
这样设计符合关系模型设计范式,但是又引入了一个新的问题,设备ID如何生成。通常使用如下两种方法:
- 使用设备信息表的自增主键值
- 通过设备信息哈希生成
2. 示例
下面举例说明,如何接入时序数据。 假设要采集风机的转速、温度和湿度。风机设备包含名称、编号、区域、坐标等属性。
2.1 数据表
按照如上方法,需要准备如下两张表:
- tags表,即风机信息表
字段 | 类型 | 描述 |
---|---|---|
tag_id | int | tag_id为风机的唯一id,用自增主键(SERIAL类型) |
name | text | 风机名称 |
serial_number | text | 风机序列号 |
region | text | 风机所属区域 |
longitude | float4 | 风机经度 |
latitude | float4 | 风机纬度 |
- 风机指标表
字段 | 类型 | 描述 |
---|---|---|
tag_id | int | 风机id |
time | timestamp | 时序数据的时间戳 |
speed | float4 | 风机转速 |
temperature | float4 | 风机温度 |
humidity | float4 | 风机湿度 |
2.2 创建数据库和表
2.2.1 创建数据库
和其他数据库一样,在MatrixDB中,数据表是按库组织起来的,所以先创建数据库stats
。
可以连接默认数据库postgres
,然后使用CREATE DATABASE
创建:
[mxadmin@mdw ~]$ psql postgres
psql (12)
Type "help" for help.
postgres=# CREATE DATABASE stats;
CREATE DATABASE
也可以直接使用MatrixDB的命令行工具createdb创建:
[mxadmin@mdw ~]$ createdb stats
2.2.2 连接数据库
创建好数据库后,要连接进去才能使用。在MatrixDB中每个连接只能隶属于一个固定的库。
可以在执行psql
命令尾随数据库参数连接:
[mxadmin@mdw ~]$ psql stats
psql (12)
Type "help" for help.
stats=#
也可以从其他数据库连接中,使用\c
元命令,切换连接到新的数据库中:
postgres=# \c stats
You are now connected to database "stats" as user "mxadmin".
2.2.3 创建表
连接数据库后,开始创建数据表:
-
创建tags表:
CREATE TABLE tags( tag_id serial, name text, serial_number text, region text, longitude float4, latitude float4 ) Distributed replicated;
-
创建指标表:
CREATE TABLE metrics( time timestamp with time zone, tag_id int, speed float4, temperature float4, humidity float4 ) Distributed by (tag_id);
从上面的建表语句会发现,两个表的分布方式不同。
tags
表使用得是Distributed replicated
metrics
使用得是Distributed by (tag_id)
因为:
- 设备信息相对较少,而且要经常做连接,所以分布方式设置为复制
- 指标表数据量大,用tag_id作为分布键,使得相同设备的数据分布在相同节点上
有关数据分布更详细的介绍请参考数据分布模型
- 为tag表建立唯一索引:
因为tag表中设备信息必须唯一,所以需要为标识设备唯一属性的信息建立唯一索引,便于应用端维护设备信息。这里假设设备编号用来标志设备的唯一性,所以建立如下唯一索引:
CREATE UNIQUE INDEX ON tags(serial_number);
2.2.4 写入数据
应用方在进行指标数据接入的时候,需要按如下步骤进行:
- 将设备标识转化为设备ID
- 将设备ID和指标信息发送到MatrixDB
那么设备标识如何转换成ID呢? 设备ID并不是设备自身的一个属性,不会随指标数据发送过来,需要应用方进行转换。
最简单的做法就是每次根据设备编号查库,如果存在则直接读取;不存在则插入新设备并生成ID。
这种方法需要每个指标在插入前都要查库,大大增加数据库负载,影响吞吐量。所以推荐做法是,在应用方维护一个内存哈希表。每条数据在到来的时候从内存哈希表查询设备ID,存在则直接使用;不存在则插库获取设备ID并更新哈希表。
2.3 数据查询
既然设备信息和指标数据分开到两个表存储。在做查询的时候,如果既要获得指标统计信息又要获得设备信息,要基于设备ID做连接。
如下SQL统计了'2021-07-01'日每个风机的平均温度、平均湿度和最大转速:
SELECT tags.name,
AVG(metrics.temperature),
AVG(metrics.humidity),
MAX(metrics.speed)
FROM tags JOIN metrics USING (tag_id)
WHERE metrics.time >= '2021-07-01'
AND metrics.time < '2021-07-02'
GROUP BY tags.tag_id, tags.name;
3. 可扩展字段
从上文介绍可以看出,MatrixDB是关系型数据库,在数据建模时使用的是关系数据库的范式。关系模型固然好用,但在做时序采集时也面临一些挑战,考虑如下场景:
- 要采集的指标过多,超过了PostgreSQL的最多1600列限制
- 不同型号设备采集指标集合差别较大,导致在回传数据时有大量列值为NULL
- 无法预知指标集,即表schema可能要经常变
针对如上问题,MatrixDB技术团队开发了mxkv自定义数据类型,提供了kv键值存储结构,完美解决了如上场景的问题。
如下图所示,mxkv是一个键值类型,内部可以存储任意数量的键值。键数量无限制,并且可以任意增加新键。对于不确定的指标将其存在mxkv字段里即可。实际存储空间开销取决于键值数量和大小。
mxkv的使用请参考mxkv