存储引擎选择与转换

1. 时序存储引擎

MatrixDB在时序场景中,有两种常用类型的表:

类型 描述
Heap Heap表为传统PostgreSQL数据库提供的存储类型,又称堆表。
该类型的表支持大量并发读写、事务、索引等特性。
Mars Mars表为MatrixDB自研的存储类型。
支持压缩、列存、预聚集,在分析场景中性能极高。
但要求数据按顺序插入,并且暂不支持更新和删除。

所以,为了最大化时序数据写入与查询分析性能,在生产环境中需要两种类型的表搭配使用。数据写入时先写入Heap表,因为:

  • Heap并发写性能高
  • 时序数据接入时可能在一个较小的时间窗口内乱序上报、延迟上报、同设备分批次上报,无法保证按时间递增,需要更新操作

2. 存储引擎的分区管理与转换

在MatrixDB中,使用分区技术来搭配两种表类型,按时间戳来做数据分区。热数据先写入Heap类型的热分区,在确定已接入数据不会发生修改并且不会上报更早的数据后,将Heap热分区转换为Mars冷分区。

分区时间跨度根据数据量不同而不同。比如:数据量大,分区粒度要小,可以按天或小时来分区;数据量小,分区粒度可以调大,按周或月来分区。

MatrixDB提供了分区管理的UDF,需要先建立相关扩展组件:

CREATE EXTENSION matrixts;
CREATE EXTENSION mars;

以记录磁盘读写量为例,做冷热分级存储的用法如下:

2.1 建立时序表

CREATE TABLE disk(
    time timestamp with time zone,
    tag_id int,
    read float,
    write float
)
Distributed by (tag_id)
Partition by range(time);

通过如上SQL语句,建立了一张按时间戳分区的磁盘读写时序表,分布键是tag_id。

2.2 建立分区模板

建立好时序表后,下面通过调用mars扩展提供的build_timeseries_table函数建立分区模板:

SELECT mars.build_timeseries_table('disk','tagkey="tag_id", timekey="time", timebucket="1 day"');

在建立分区模板的过程中,需要提供tag键和时间戳的列名,还有时间单位。

分区模板表不会在DROP原表的时候自动删除,需要手动清理。如果要删除原表,正确操作如下:

SELECT mars.destroy_timeseries_table('disk');
DROP TABLE disk;

2.3 创建分区

建立好分区模板,下面调用mars扩展提供的add_partition函数建立分区:

SELECT mars.add_partition('disk', '2021-04-01', '2021-05-01','1 day');

如上SQL建立了从2021-04-01到2021-05-01时间段的分区,时间单位为1天。

创建好分区,调用list_partition函数查看分区信息:

ymatrix=# SELECT * from mars.list_partition('disk');
                    relname                    | storage
-----------------------------------------------+---------
 heap_disk_20210401000000000_20210402000000000 | heap
 heap_disk_20210402000000000_20210403000000000 | heap
 heap_disk_20210403000000000_20210404000000000 | heap
 heap_disk_20210404000000000_20210405000000000 | heap
 heap_disk_20210405000000000_20210406000000000 | heap
 heap_disk_20210406000000000_20210407000000000 | heap
 heap_disk_20210407000000000_20210408000000000 | heap
 heap_disk_20210408000000000_20210409000000000 | heap
 heap_disk_20210409000000000_20210410000000000 | heap
 heap_disk_20210410000000000_20210411000000000 | heap
 heap_disk_20210411000000000_20210412000000000 | heap
 heap_disk_20210412000000000_20210413000000000 | heap
 heap_disk_20210413000000000_20210414000000000 | heap
 heap_disk_20210414000000000_20210415000000000 | heap
 heap_disk_20210415000000000_20210416000000000 | heap
 heap_disk_20210416000000000_20210417000000000 | heap
 heap_disk_20210417000000000_20210418000000000 | heap
 heap_disk_20210418000000000_20210419000000000 | heap
 heap_disk_20210419000000000_20210420000000000 | heap
 heap_disk_20210420000000000_20210421000000000 | heap
 heap_disk_20210421000000000_20210422000000000 | heap
 heap_disk_20210422000000000_20210423000000000 | heap
 heap_disk_20210423000000000_20210424000000000 | heap
 heap_disk_20210424000000000_20210425000000000 | heap
 heap_disk_20210425000000000_20210426000000000 | heap
 heap_disk_20210426000000000_20210427000000000 | heap
 heap_disk_20210427000000000_20210428000000000 | heap
 heap_disk_20210428000000000_20210429000000000 | heap
 heap_disk_20210429000000000_20210430000000000 | heap
 heap_disk_20210430000000000_20210501000000000 | heap
(30 rows)

如上SQL列出来了disk表包含的所有分区子表,包括子表名和存储类型。

2.4 时序数据写入表

insert into disk values(now(), ...., ..., ...);

因为表是按时间分区的,所以直接向主表插入数据即可,数据会自动落到对应的分区上。

2.5 分区替换

当4月1日的时序数据采集完毕后,即不会再有4月1日的时序数据接入,要将4月1日的数据从热分区转换为冷分区。

Mars扩展提供了compress_partition函数,用来做分区转换:

ymatrix=# select mars.compress_partition('heap_disk_20210401000000000_20210402000000000');
              compress_partition
-----------------------------------------------
 mars_disk_20210401000000000_20210402000000000
(1 row)

如上SQL将4月1日对应的分区做了转换,转换后再调用list_partition函数,可看到转换后的分区表名和存储类型:

ymatrix=# SELECT * from mars.list_partition('disk');
                    relname                    | storage
-----------------------------------------------+---------
 heap_disk_20210402000000000_20210403000000000 | heap
 heap_disk_20210403000000000_20210404000000000 | heap
 heap_disk_20210404000000000_20210405000000000 | heap
 heap_disk_20210405000000000_20210406000000000 | heap
 heap_disk_20210406000000000_20210407000000000 | heap
 heap_disk_20210407000000000_20210408000000000 | heap
 heap_disk_20210408000000000_20210409000000000 | heap
 heap_disk_20210409000000000_20210410000000000 | heap
 heap_disk_20210410000000000_20210411000000000 | heap
 heap_disk_20210411000000000_20210412000000000 | heap
 heap_disk_20210412000000000_20210413000000000 | heap
 heap_disk_20210413000000000_20210414000000000 | heap
 heap_disk_20210414000000000_20210415000000000 | heap
 heap_disk_20210415000000000_20210416000000000 | heap
 heap_disk_20210416000000000_20210417000000000 | heap
 heap_disk_20210417000000000_20210418000000000 | heap
 heap_disk_20210418000000000_20210419000000000 | heap
 heap_disk_20210419000000000_20210420000000000 | heap
 heap_disk_20210420000000000_20210421000000000 | heap
 heap_disk_20210421000000000_20210422000000000 | heap
 heap_disk_20210422000000000_20210423000000000 | heap
 heap_disk_20210423000000000_20210424000000000 | heap
 heap_disk_20210424000000000_20210425000000000 | heap
 heap_disk_20210425000000000_20210426000000000 | heap
 heap_disk_20210426000000000_20210427000000000 | heap
 heap_disk_20210427000000000_20210428000000000 | heap
 heap_disk_20210428000000000_20210429000000000 | heap
 heap_disk_20210429000000000_20210430000000000 | heap
 heap_disk_20210430000000000_20210501000000000 | heap
 mars_disk_20210401000000000_20210402000000000 | mars
(30 rows)

可以看到,转换后的分区名前缀从heap变成了mars,存储类型也发生了相应的变化。

2.6 删除旧表

compress_partition函数会做冷热数据分区转换,但是原Heap分区子表的清理需要手动完成:

DROP TABLE heap_disk_20210401000000000_20210402000000000;

3. 直接使用Mars引擎

Mars引擎不仅仅可以搭配在分区表中,也可以单独创建使用。

当用户有一批静态数据需要使用Mars引擎的高压缩、高性能优势来优化查询的时候,可以手动创建Mars表,并将数据排序后导入。

先创建扩展:

CREATE EXTENSION matrixts;
CREATE EXTENSION mars;

再创建mars表,建表时使用USING Mars来指定存储引擎:

CREATE TABLE disk_mars(
    time timestamp with time zone,
    tag_id int,
    read float,
    write float
)
USING Mars
WITH (tagkey="tag_id", timekey="time", timebucket="8 hours")
Distributed by (tag_id);

WITH关键字中指定了Mars表的meta信息,包括:

  1. tag_id:设备ID字段
  2. timekey:时间戳字段
  3. timebucket:每个行组(Row Group)的时间跨度

3.1 压缩存储

Mars表支持列式压缩。压缩不仅可以减少存储量,同时也能降低查询成本。

Mars表支持4种压缩类型:

  1. rle_type
  2. zstd
  3. zlib
  4. lz4

创建Mars表时,通过compresstype指定列的压缩类型。同时还要通过compresslevel指定压缩级别(0-9),级别越高,压缩率就越高,占用空间更小,但对计算性能的消耗也更大。

如下sql创建的Mars表,为不同的列指定了不同的压缩类型:

CREATE TABLE disk_mars (
    time timestamp with time zone ENCODING (compresstype=rle_type, compresslevel=1),
    tag_id int ENCODING (compresstype=zstd, compresslevel=1),
    read float ENCODING (compresstype=zlib, compresslevel=1),
    write float ENCODING (compresstype=lz4, compresslevel=1)
)
USING Mars
WITH (tagkey="tag_id", timekey="time", timebucket="8 hours")
Distributed by (tag_id);

3.2 向Mars表导入数据

Mars表要求数据按tag_id和时间戳排序后再灌入,否则会导致查询结果错误。

将heap表数据导入到Mars表:

INSERT INTO disk_mars SELECT * FROM disk_heap ORDER BY tag_id, time;

也可以将外部表数据导入到Mars表:

INSERT INTO disk_mars SELECT * FROM ext ORDER BY tag_id, time;