快速入门

Ganos TSDB是在PolarDB PostgreSQL基础上以插件的形式实现的时序数据库,它继承了PolarDB PostgreSQL集群拥有的共享存储、一写多读、备份恢复等一切能力,除此之外它可完全兼容开源时序数据库TimescaleDB Apache 2.0版本,并提供连续聚合、时序压缩、统计分析等时序数据高级功能。本文介绍时序数据库的基本概念,以及Ganos TSDB的基本用法,包括启用与升级维护,同时介绍时序数据库的使用方法。

前提条件

支持的PolarDB PostgreSQL的版本如下:

PostgreSQL 14(内核小版本2.0.14.13.26.0及以上)。

说明

您可在控制台查看内核小版本号,也可以通过SHOW polardb_version;语句查看。如未满足内核小版本要求,请升级内核小版本

基本概念

  • 时序数据(Time Series Data):指按照时间顺序记录的数据序列。这类数据的特点是数据点不仅包含数值信息,还关联了具体的时间戳,反映了某一变量随时间变化的趋势或模式。时序数据广泛应用于多个领域,如金融交易、气象观测、传感器监测、网站流量分析、疾病传播研究等。时序数据的关键要素包括:

    • 时间戳:每个数据点都有一个明确的时间标记,表明数据的采集时间。

    • 观测值:在各个时间点上测量或记录到的数据值。

    • 顺序性:数据点按照时间的先后顺序排列,这种顺序反映了数据之间的自然时间关系,使得时序数据具有时间序列的特性。

    • 趋势和周期性:时序数据分析中常关注数据中的长期趋势、季节性波动、周期性变化以及随机波动等特征。

  • 度量(Metrics):时序数据监测指标,如温度、股价、网络流量等任何可以量化测量的数值,度量可以帮助理解时序数据的行为模式、预测未来趋势,并能及时发现并响应异常情况。

  • 聚合(Aggregation):指在时间序列数据分析中,将较高频率的数据聚合到较低频率的过程,简单来说,就是将数据按时间维度进行合并或汇总,从而减少数据的粒度,但同时能保留数据的主要趋势或特征。这种处理方式对于数据降维、趋势分析、异常检测以及预测模型的构建都非常有用。

  • 降采样(Downsampling):时序数据处理的一个常见操作,可以减少时间序列数据的采样频率,即降低数据点的数量,同时尝试保留原始数据的关键特征或趋势。时序降采样的几种常见方法:

    • 直接抽样(Decimation):这是最直接的方法,简单地每隔N个点取一个数据点。例如,如果原序列每秒采样10次,您可以选择只保留每5个点中的第一个,从而实现每秒2次的采样率。这种方法简单但可能会丢失高频信息。

    • 平均值法(Averaging):在降采样前,先对连续的数据点进行平均处理。例如,将连续的5个点取平均值作为新的一个数据点。这种方法有助于平滑数据,减少噪声,但可能会模糊快速变化的细节。

    • 中值法(Median):与平均值法类似,但使用连续数据点的中值代替平均值,这在处理包含异常值的时间序列时更为稳健。

    • 最大值/最小值法:选取连续数据点中的最大值或最小值作为新数据点,适用于需要保留峰值或谷值的场景。

    • 重采样(Resampling):通过插值等方法调整数据到新的时间点上,然后在新的时间线上进行均匀采样。常见的重采样方法有线性插值、最近邻插值、立方插值等。重采样方法更加灵活,可以根据需求调整到任意的采样频率,同时尽可能保持原数据的形态。

    • 模型预测降采样:利用统计模型或机器学习模型预测在较低采样率下的数据点,比如使用ARIMA、LSTM等模型预测每个新时间点的值,然后使用这些预测值作为降采样后的数据。这种方法可以更智能地降采样,但计算成本较高。

Ganos TSDBTimescaleDB对比

  • 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参数后集群将会重启,请在修改参数前做好业务安排,谨慎操作。

创建插件

Ganos TSDB插件依赖TimescaleDB插件,因此您可以使用以下任一方式创建。

  • 创建ganos_tsdb时指定CASCADE,将同时创建TimescaleDB插件。

    CREATE EXTENSION ganos_tsdb CASCADE;
  • 手动创建。先创建TimescaleDB,再创建ganos_tsdb

    CREATE EXTENSION timescaledb;
    CREATE EXTENSION ganos_tsdb;
说明
  • 如在安装插件时遇到ERROR: Disable the injection of custom functions when creating extension: metadata_insert_trigger (21128)类似错误,请联系我们开放相关权限后再执行安装插件语句。

  • 为避免权限问题,建议您将扩展安装在PUBLIC模式下。

    CREATE EXTENSION ganos_tsdb WITH SCHEMA PUBLIC CASCADE;

升级插件

对于已经安装了Ganos TSDB或者TimescaleDB插件的集群,可以通过以下命令升级:

--先升级timescaledb
ALTER EXTENSION timescaledb UPDATE;

--再升级ganos_tsdb
ALTER EXTENSION ganos_tsdb UPDATE;

时序超表

超表(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

原表中具有时序特征的时间字段名,支持如下数据类型:

  • TIMESTAMP/TIMESTAMPTZ/DATE类型。

  • 被视为微秒的整数类型。

  • INTERVAL类型。

partitioning_column_name

分区字段名,默认按原表的时间字段分区。

number_partitions

分区数量。

associated_schema_name

指定超表所在的Schema。

associated_table_prefix

由于超表由一系列的分块表组成,块表会在内部自动创建,这里可指定块表的前缀。

chunk_time_interval

超表分块的时间间隔,默认按7 天分块。

create_default_indexes

是否在超表的时间字段上创建B树索引,取值范围如下:

  • true(默认):在超表的时间字段上创建B树索引。

  • false:不在超表的时间字段上创建B树索引。

if_not_exists

如果超表已经创建,是否报错,取值范围如下:

  • true:如果超表已经创建,则报错。

  • false(默认):如果超表已经创建,不报错。

partitioning_func

如果使用空间分区,可指定自定义的空间分区函数。

migrate_data

如果原表中已经存在数据,是否将数据转移至超表,取值范围如下:

  • true:如果原表中已经存在数据,将数据转移至超表。

  • false(默认):如果原表中已经存在数据,不将数据转移至超表

chunk_target_size

指定块表的大小(示例:'1000MB', 'estimate', or 'off')。

chunk_sizing_func

chunk_target_size配合使用,指定自定义的函数以统计块表的时间间隔,并应用在新的块表中。

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。

示例

以交易数据为例,创建时序超表:

  1. 创建普通表:

    CREATE TABLE transaction_data(
       tm TIMESTAMPTZ NOT NULL,
       id INT NOT NULL,
       price double precision);
  2. 设置普通表的复制标识,必须为DEFAULT模式:

    ALTER TABLE transaction_data replica IDENTITY DEFAULT;
  3. 将普通表转换为超表:

    SELECT create_hypertable('transaction_data', 'tm', chunk_time_interval => INTERVAL '1 day');
    说明

    如果该表已存在数据,请在转换为超表时增加migrate_data参数并设置为true,但如果表数据量较大,转换过程可能消耗较长时间。

  4. (可选)修改超表分区间隔。

    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

是否自动运行,默认为true即自动运行。

check_config

检查配置参数config是否有效的函数,默认为NULL。

fixed_schedule

是否按固定时间间隔运行该任务,默认为true

timezone

时区,默认为NULL。

示例
  1. 基础数据准备。创建需要执行的存储过程。

    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
    $$;
  2. 创建任务。返回任务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

需要修改的目标任务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

需要运行的目标任务id

示例
CALL run_job(1002);

删除任务

删除任务。

void delete_job(job_id int);

具体参数解释如下:

参数名称

描述

job_id

需要删除的目标任务id

示例
SELECT delete_job(1002);

刷新连续聚合

手动刷新

手动刷新连续聚合的存储过程。

refresh_continuous_aggregate(
    cagg     REGCLASS,
    window_start             "any",
    window_end               "any"
    );

具体的参数解释如下:

参数名称

描述

cagg

需要刷新的连续聚合名称或OID。

window_start

刷新窗口开始时间,类型需与连续聚合时间字段一致。

  • 如果刷新窗口开始时间非整单位时间,可配合date_trunc使用。例如,按分钟进行连续聚合,刷新窗口时间为2024-03-11 13:01:29,则可设置刷新窗口开始时间为2024-03-11 13:01:00

  • 不建议将该值设置为NULL。当设置为NULL时,数据库将从表中获取起始时间,如果表数据量过大,则需要消耗较长时间等待数据刷新完成。

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_job实现自动刷新任务,使用更加灵活。但请区分任务启动时间initial_start和刷新窗口开始时间window_start,启动时间需为整时单位,若为NULL表示立即执行,若为将来的时间,则将在设定的时间后开始执行。

示例

  1. 定义刷新策略。

    CREATE OR REPLACE PROCEDURE transaction_min_cagg_refresh() LANGUAGE PLPGSQL AS
    $$
    DECLARE
    BEGIN
        -- 刷新窗口开始时间为'2024-03-11 00:00:00+08',刷新窗口结束时间为NULL,表示刷新到数据表的最新时间
        CALL refresh_continuous_aggregate('transaction_min_cagg', '2024-03-11 00:00:00+08'::timestamptz, NULL);
    END
    $$;
  2. 将刷新任务添加至自动化运行队列。此处设置为1分钟自动刷新一次,刷新任务开始执行的时间为'2024-04-01 00:00:00+08'

    SELECT add_job('transaction_min_cagg_refresh','1 min', initial_start => '2024-04-01 00:00:00+08'::timestamptz);

使用add_continuous_aggregate_policy函数

针对指定的连续聚合创建刷新策略,用于定期自动刷新。

integer add_continuous_aggregate_policy(
                cagg REGCLASS,
                start_offset "any",
                end_offset "any",
                schedule_interval INTERVAL,
                if_not_exists BOOL = false,
                initial_start TIMESTAMPTZ = NULL,
                timezone TEXT = NULL
                );

具体的参数解释如下:

参数名称

描述

cagg

需要刷新的连续聚合名称或OID。

start_offset

以函数执行时间为基准,定义开始刷新聚合的偏移量,作为刷新窗口的开始时间。如果设置为NULL,即刷新窗口的起始时间为超表中数据的最小时间。

start_offset值必须大于end_offset值。

end_offset

以函数执行时间为基准,定义聚合结束的偏移量,作为刷新窗口的结束时间。如果设置为NULL,即刷新窗口的结束时间为超表中数据的最大时间。

schedule_interval

刷新间隔时间,默认为1天。

if_not_exists

如果刷新策略已经创建,是否报错,取值范围如下:

  • true:如果刷新策略已经创建,不报错只告警。

  • false(默认):如果刷新策略已经创建,则报错。

initial_start

函数首次执行时间,默认为NULL。如果指定该值,则与schedule_interval参数配合使用。

time_zone

时区,默认为NULL,表示使用UTC时间。

示例:

SELECT add_continuous_aggregate_policy('transaction_min_cagg',
    start_offset => '1 hour', -- 处理1小时前的数据(确保数据完整)
    end_offset => INTERVAL '0', -- 处理到当前时间
    schedule_interval => INTERVAL '1 sec' -- 每1秒刷新一次
    );

实时查询

在进行连续聚合查询时,如果未启用实时查询功能,则只能访问当前已完成聚合的数据。您可启用实时查询功能查询新接收但尚未经过聚合的数据。

  • 启用实时查询

    ALTER MATERIALIZED VIEW transaction_min_cagg SET (timescaledb.materialized_only = false);
  • 关闭实时查询

    ALTER MATERIALIZED VIEW transaction_min_cagg SET (timescaledb.materialized_only = true);

嵌套聚合

嵌套聚合是指在连续聚合生成的物化视图基础上再创建连续聚合,例如在按小时聚合结果基础上创建按天聚合,在按天聚合基础上创建按月聚合等,一方面减少数据冗余,另一方面提升聚合性能。

  1. 创建第一层聚合,此处按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;
  2. 创建嵌套聚合,在按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;