Ganos TSDB是在PolarDB PostgreSQL版基础上以插件的形式实现的时序数据库,它继承了PolarDB PostgreSQL版集群拥有的共享存储、一写多读、备份恢复等一切能力,除此之外它可完全兼容开源时序数据库TimescaleDB Apache 2.0版本,并提供连续聚合、时序压缩、统计分析等时序数据高级功能。本文介绍时序数据库的基本概念,以及Ganos TSDB的基本用法,包括启用与升级维护,同时介绍时序数据库的使用方法。
前提条件
支持的PolarDB PostgreSQL版的版本如下:
PostgreSQL 14(内核小版本2.0.14.13.26.0及以上)。
基本概念
时序数据(Time Series Data):指按照时间顺序记录的数据序列。这类数据的特点是数据点不仅包含数值信息,还关联了具体的时间戳,反映了某一变量随时间变化的趋势或模式。时序数据广泛应用于多个领域,如金融交易、气象观测、传感器监测、网站流量分析、疾病传播研究等。时序数据的关键要素包括:
时间戳:每个数据点都有一个明确的时间标记,表明数据的采集时间。
观测值:在各个时间点上测量或记录到的数据值。
顺序性:数据点按照时间的先后顺序排列,这种顺序反映了数据之间的自然时间关系,使得时序数据具有时间序列的特性。
趋势和周期性:时序数据分析中常关注数据中的长期趋势、季节性波动、周期性变化以及随机波动等特征。
度量(Metrics):时序数据监测指标,如温度、股价、网络流量等任何可以量化测量的数值,度量可以帮助理解时序数据的行为模式、预测未来趋势,并能及时发现并响应异常情况。
聚合(Aggregation):指在时间序列数据分析中,将较高频率的数据聚合到较低频率的过程,简单来说,就是将数据按时间维度进行合并或汇总,从而减少数据的粒度,但同时能保留数据的主要趋势或特征。这种处理方式对于数据降维、趋势分析、异常检测以及预测模型的构建都非常有用。
降采样(Downsampling):时序数据处理的一个常见操作,可以减少时间序列数据的采样频率,即降低数据点的数量,同时尝试保留原始数据的关键特征或趋势。时序降采样的几种常见方法:
直接抽样(Decimation):这是最直接的方法,简单地每隔N个点取一个数据点。例如,如果原序列每秒采样10次,您可以选择只保留每5个点中的第一个,从而实现每秒2次的采样率。这种方法简单但可能会丢失高频信息。
平均值法(Averaging):在降采样前,先对连续的数据点进行平均处理。例如,将连续的5个点取平均值作为新的一个数据点。这种方法有助于平滑数据,减少噪声,但可能会模糊快速变化的细节。
中值法(Median):与平均值法类似,但使用连续数据点的中值代替平均值,这在处理包含异常值的时间序列时更为稳健。
最大值/最小值法:选取连续数据点中的最大值或最小值作为新数据点,适用于需要保留峰值或谷值的场景。
重采样(Resampling):通过插值等方法调整数据到新的时间点上,然后在新的时间线上进行均匀采样。常见的重采样方法有线性插值、最近邻插值、立方插值等。重采样方法更加灵活,可以根据需求调整到任意的采样频率,同时尽可能保持原数据的形态。
模型预测降采样:利用统计模型或机器学习模型预测在较低采样率下的数据点,比如使用ARIMA、LSTM等模型预测每个新时间点的值,然后使用这些预测值作为降采样后的数据。这种方法可以更智能地降采样,但计算成本较高。
Ganos TSDB与TimescaleDB对比
TimescaleDB:基于PostgreSQL构建的开源时序数据库(Time-Series Database),专为处理时间序列数据(如传感器数据、监控指标、金融数据等)优化,其设计目标是在保留PostgreSQL完整SQL功能的同时,提供时序数据的高效存储、快速写入和复杂查询能力。
Ganos TSDB:在开源时序数据库TimescaleDB基础上开发的时序引擎,依托于PolarDB PostgreSQL版集群,以插件的方式提供,同时完全继承PolarDB共享存储、一写多读、备份恢复等一切能力。
区别
两者主要在其提供的功能方面存在差异。TimescaleDB Apache 2 Edition是在Apache 2.0协议许可下发布的TimescaleDB版本。Apache 2.0协议开源许可证允许任何人使用这些代码并将其作为服务提供,但仅提供基础功能。
对此,Ganos TSDB在完全兼容TimescaleDB的基础上,额外提供时序连续聚合、数据压缩、OSS冷热分层存储等高级功能。
启用时序数据库
在PolarDB PostgreSQL版集群中启用时序数据库能力,需要将timescaledb
添加到shared_preload_libraries参数中。您可以通过控制台修改shared_preload_libraries参数。
修改shared_preload_libraries
参数后集群将会重启,请在修改参数前做好业务安排,谨慎操作。
创建插件
升级插件
时序超表
超表(Hypertable)是时序数据库提供的一种特殊功能的表,可轻松处理时间序列数据,使用常规表可以完成的所有操作都可以使用超表完成。
功能特性
超表可自动按时间对数据进行分区。与超表的交互方式与常规表相同,但超表还具有一些额外功能,可更轻松地管理时间序列数据。
在时序数据库中,超表与常规表并存。使用超表来存储时间序列数据,这可以提高数据写入和查询性能,并支持在超表上使用序列功能函数。
借助超表,时序数据库可以根据时间参数对时间序列数据进行分区,在后台,由数据库自动执行设置和维护超表分区的工作。
创建超表
将一张普通数据表转为超表的语法如下:
SELECT create_hypertable(
'table_name',
'time_column_name',
'partitioning_column_name',
number_partitions,
'associated_schema_name',
'associated_table_prefix',
chunk_time_interval,
create_default_indexes,
if_not_exists,
partitioning_func,
migrate_data,
chunk_target_size,
chunk_sizing_func,
time_partitioning_func
);
具体参数解释如下:
参数名称 | 是否必选 | 描述 |
table_name | 是 | 需要转为超表的数据表名称或OID,为了区分,以下称其为原表,转为超表后,名称与原表同名。 |
time_column_name | 是 | 原表中具有时序特征的时间字段名,支持如下数据类型:
|
partitioning_column_name | 否 | 分区字段名,默认按原表的时间字段分区。 |
number_partitions | 否 | 分区数量。 |
associated_schema_name | 否 | 指定超表所在的Schema。 |
associated_table_prefix | 否 | 由于超表由一系列的分块表组成,块表会在内部自动创建,这里可指定块表的前缀。 |
chunk_time_interval | 否 | 超表分块的时间间隔,默认按7 天分块。 |
create_default_indexes | 否 | 是否在超表的时间字段上创建B树索引,取值范围如下:
|
if_not_exists | 否 | 如果超表已经创建,是否报错,取值范围如下:
|
partitioning_func | 否 | 如果使用空间分区,可指定自定义的空间分区函数。 |
migrate_data | 否 | 如果原表中已经存在数据,是否将数据转移至超表,取值范围如下:
|
chunk_target_size | 否 | 指定块表的大小(示例: |
chunk_sizing_func | 否 | 与 |
time_partitioning_func | 否 | 指定用于时间分区的分区函数。 |
超表的分区
在创建和使用超表时,它会自动按时间对数据进行分区,也可以按空间对数据进行分区。
每个超表由称为块(chunk)的子表组成,每个块分配一个时间范围,并且仅包含该范围内的数据。如果超表也按空间分区,则每个块也会分配空间值的子集。
超表的每个块仅保存特定时间范围内的数据。当插入尚无块的时间范围内的数据时,会自动创建一个块来存储它。
默认情况下,按7天时间间隔进行分区块,也可以使用以下SQL语句修改分区时间间隔。
SELECT set_chunk_time_interval(
'table_name',
chunk_time_interval,
'dimension_name'
);
具体参数解释如下:
参数名称 | 描述 |
table_name | 超表名称。 |
chunk_time_interval | 超表分块时间间隔。 |
dimension_name | 可选参数,指定分区(维度)策略,默认为NULL。 |
示例
以交易数据为例,创建时序超表:
创建普通表:
CREATE TABLE transaction_data( tm TIMESTAMPTZ NOT NULL, id INT NOT NULL, price double precision);
设置普通表的复制标识,必须为DEFAULT模式:
ALTER TABLE transaction_data replica IDENTITY DEFAULT;
将普通表转换为超表:
SELECT create_hypertable('transaction_data', 'tm', chunk_time_interval => INTERVAL '1 day');
说明如果该表已存在数据,请在转换为超表时增加
migrate_data
参数并设置为true
,但如果表数据量较大,转换过程可能消耗较长时间。(可选)修改超表分区间隔。
SELECT set_chunk_time_interval('transaction_data', chunk_time_interval => INTERVAL '2 day');
连续聚合
连续聚合(continuous aggregates)是一种自动化的预计算机制,通过定期或实时更新预先定义的聚合结果(如每小时平均值),将复杂查询转换为对预计算结果的快速读取。它类似于物化视图(Materialized View),但针对时序数据进行优化,支持增量更新,避免重复计算历史数据,旨在加速超大数据集查询。
功能优势
查询加速:直接查询预计算结果,避免全表扫描原始数据。
节约资源:减少CPU和内存的实时计算消耗。
自动化维护:自动处理新旧数据的聚合更新,无需手动触发。
创建连续聚合
CREATE MATERIALIZED VIEW transaction_min_cagg
WITH (timescaledb.continuous) -- 声明为连续聚合
AS
SELECT id,
time_bucket(INTERVAL '1 min', tm) AS bucket, -- 按1分钟间隔做一次聚合
AVG(price),
MAX(price),
MIN(price)
FROM transaction_data
GROUP BY id, bucket;
连续聚合数据存储
连续聚合结果保存在独立的物化视图中,数据按时间分块(Chunk)存储,与超表数据分区对齐。
任务调度
连续聚合是通过任务调度实现数据写入及刷新的。通过任务调度功能,可实现自动或手动对聚合结果进行刷新。
创建任务
创建任务,该任务可自动化调度某个函数或存储过程。
integer add_job(
proc REGPROC,
schedule_interval INTERVAL,
config JSONB DEFAULT NULL,
initial_start TIMESTAMPTZ DEFAULT NULL,
scheduled BOOL DEFAULT true,
check_config REGPROC DEFAULT NULL,
fixed_schedule BOOL DEFAULT TRUE,
timezone TEXT DEFAULT NULL
);
具体的参数解释如下:
参数名称 | 描述 |
proc | 需要执行的函数或存储过程的名称或OID。 |
schedule_interval | 任务调度时间间隔。 |
config | 任务配置参数。 |
initial_start | 任务运行开始时间,默认为当前时间。 |
scheduled | 是否自动运行,默认为 |
check_config | 检查配置参数 |
fixed_schedule | 是否按固定时间间隔运行该任务,默认为 |
timezone | 时区,默认为NULL。 |
示例
基础数据准备。创建需要执行的存储过程。
CREATE OR REPLACE PROCEDURE user_defined_action(job_id int, config jsonb) LANGUAGE PLPGSQL AS $$ BEGIN RAISE NOTICE 'Executing action % with config %', job_id, config; END $$;
创建任务。返回任务
id
。SELECT add_job('user_defined_action','1 hour'); SELECT add_job('user_defined_action','1h', initial_start => '2024-03-11 00:00:00');
修改任务
修改已创建的任务。
void alter_job( job_id INTEGER,
schedule_interval INTERVAL = NULL,
max_runtime INTERVAL = NULL,
max_retries INTEGER = NULL,
retry_period INTERVAL = NULL,
scheduled BOOL = NULL,
config JSONB = NULL,
next_start TIMESTAMPTZ = NULL,
if_exists BOOL = FALSE,
check_config REGPROC = NULL,
fixed_schedule BOOL = NULL,
initial_start TIMESTAMPTZ = NULL,
timezone TEXT DEFAULT NULL
);
具体参数解释如下:
此处仅为部分参数说明,其他参数介绍可参见创建任务参数说明。
参数名称 | 描述 |
job_id | 需要修改的目标任务 |
max_runtime | 任务运行最大时间,默认为NULL。 |
max_retries | 任务运行失败最大重试次数,默认为NULL。 |
retry_period | 任务运行失败重试时间间隔,默认为NULL。 |
示例
SELECT alter_job(1002, schedule_interval => INTERVAL '2 hours');
手动运行任务
手动运行已存在的任务。
void run_job(job_id int);
具体参数解释如下:
参数名称 | 描述 |
job_id | 需要运行的目标任务 |
示例
CALL run_job(1002);
删除任务
删除任务。
void delete_job(job_id int);
具体参数解释如下:
参数名称 | 描述 |
job_id | 需要删除的目标任务 |
示例
SELECT delete_job(1002);
刷新连续聚合
手动刷新
手动刷新连续聚合的存储过程。
refresh_continuous_aggregate(
cagg REGCLASS,
window_start "any",
window_end "any"
);
具体的参数解释如下:
参数名称 | 描述 |
cagg | 需要刷新的连续聚合名称或OID。 |
window_start | 刷新窗口开始时间,类型需与连续聚合时间字段一致。
|
window_end | 刷新窗口结束时间,类型需与连续聚合时间字段一致。 当不明确刷新结束时间,可设置为NULL,表示刷新到数据表的最新时间。 |
注意事项
刷新窗口的时间与表中数据时间相匹配,刷新连续聚合时,只会刷新完全匹配的时间窗口的数据,对于不完整的时间段,无法计算聚合。
如果多次调用
refresh_continuous_aggregate
的时间窗口存在重叠,则只针对有新增数据或修改数据的时间段做聚合,对于无数据修改的时间段不会做任何操作。refresh_continuous_aggregate
是存储过程函数,使用CALL调用。
示例
CALL refresh_continuous_aggregate('transaction_min_cagg', '2024-03-11 00:00:00+08'::timestamptz, NULL);
自动刷新
支持以下两种方式实现连续聚合的自动刷新。
使用add_continuous_aggregate_policy函数
实时查询
在进行连续聚合查询时,如果未启用实时查询功能,则只能访问当前已完成聚合的数据。您可启用实时查询功能查询新接收但尚未经过聚合的数据。
启用实时查询
ALTER MATERIALIZED VIEW transaction_min_cagg SET (timescaledb.materialized_only = false);
关闭实时查询
ALTER MATERIALIZED VIEW transaction_min_cagg SET (timescaledb.materialized_only = true);
嵌套聚合
嵌套聚合是指在连续聚合生成的物化视图基础上再创建连续聚合,例如在按小时聚合结果基础上创建按天聚合,在按天聚合基础上创建按月聚合等,一方面减少数据冗余,另一方面提升聚合性能。
创建第一层聚合,此处按1分钟聚合。
CREATE MATERIALIZED VIEW transaction_min_cagg WITH (timescaledb.continuous) AS SELECT id, time_bucket(INTERVAL '1 min', tm) AS bucket, AVG(price) as avg, MAX(price) as max, MIN(price) as min FROM transaction_data GROUP BY id, bucket;
创建嵌套聚合,在按1分钟聚合结果上创建按小时聚合。
CREATE MATERIALIZED VIEW transaction_one_hour_mview WITH (timescaledb.continuous) AS SELECT id, time_bucket(INTERVAL '1 hour', bucket) AS bucket, AVG(avg) as avg, MAX(max) as max, MIN(min) as min FROM transaction_min_cagg GROUP BY id, time_bucket(INTERVAL '1 hour', bucket);
时序压缩
当时序分区被判定为历史数据时,可以启用时序数据库提供的数据压缩功能,Ganos TSDB提供整表压缩和指定分区压缩的功能,压缩后可降低数据存储空间,压缩率可达70%以上,大大减轻存储成本。
压缩后数据只能读不能写。
卸载插件
DROP EXTENSION ganos_tsdb CASCADE;
DROP EXTENSION timescaledb CASCADE;