时序数据通常增长非常迅速。这意味着将数据聚合为有用的摘要可能会变得非常缓慢。连续聚合使数据聚合速度如闪电般快速。

如果您非常频繁地收集数据,您可能希望将数据聚合为分钟或小时。例如,如果您有一个每秒读取一次温度读数的表,您可以找到每小时的平均温度。每次运行此查询时,数据库都需要扫描整个表并每次重新计算平均值。

连续聚合是一种超表,它会在后台随着新数据的添加或旧数据的修改而自动刷新。对数据集的更改会被跟踪,并且连续聚合背后的超表会在后台自动更新。

您无需手动刷新连续聚合,它们会在后台持续且增量地更新。与常规 PostgreSQL 物化视图相比,连续聚合的维护负担也低得多,因为不是在每次刷新时都从头开始创建整个视图。这意味着您可以继续处理数据,而不是维护数据库。

由于连续聚合基于超表,因此您可以像查询其他表一样查询它们,并在连续聚合上启用压缩分层存储。您甚至可以在您的连续聚合之上创建分层连续聚合

默认情况下,查询连续聚合会为您提供实时数据。来自物化视图的预聚合数据与尚未聚合的最新数据相结合。这使您在每次查询时都能获得最新的结果。

有三种主要的简化聚合的方法:物化视图、连续聚合和实时聚合。

物化视图是标准的 PostgreSQL 功能。它们用于缓存复杂查询的结果,以便您稍后可以重复使用它。物化视图不会定期更新,尽管您可以根据需要手动刷新它们。

连续聚合是 Timescale 独有的功能。它们的工作方式类似于物化视图,但当新数据添加到数据库时,它们会在后台自动更新。连续聚合是持续且增量地更新的,这意味着与物化视图相比,它们的维护资源密集程度较低。连续聚合基于超表,您可以像查询其他表一样查询它们。

实时聚合是 Timescale 独有的功能。它们与连续聚合相同,但它们将最新的原始数据添加到先前聚合的数据中,以提供准确和最新的结果,而无需在数据写入时进行聚合。

您可以在另一个连续聚合之上创建连续聚合。这使您可以汇总不同粒度的数据。例如,您可能有一个包含秒级数据的原始超表。在超表上创建一个连续聚合以计算每小时数据。要计算每日数据,请在您的每小时连续聚合之上创建一个连续聚合。

有关更多信息,请参阅关于连续聚合之上的连续聚合的文档。

连续聚合支持以下 JOIN 功能

功能TimescaleDB < 2.10.xTimescaleDB <= 2.15.xTimescaleDB >= 2.16.x
INNER JOIN
LEFT JOIN
LATERAL JOIN
一个超表和 一个 标准 PostgreSQL 表之间的连接
一个超表和 多个 标准 PostgreSQL 表之间的连接
连接条件必须是相等条件,并且只能有 一个 JOIN 条件
任何连接条件

TimescaleDB 中的 JOINS 必须满足以下条件

  • 仅跟踪对超表的更改,当连续聚合刷新时,它们会在其中更新。不对标准 PostgreSQL 表的更改进行跟踪。
  • 您可以使用 INNERLEFTLATERAL 连接,不支持其他连接类型。
  • 不支持在连续聚合的物化超表上进行连接。
  • 分层连续聚合可以在带有 JOIN 子句的连续聚合之上创建,但它们本身不能具有 JOIN 子句。

给定以下模式

CREATE TABLE locations (
id TEXT PRIMARY KEY,
name TEXT
);
CREATE TABLE devices (
id SERIAL PRIMARY KEY,
location_id TEXT,
name TEXT
);
CREATE TABLE conditions (
"time" TIMESTAMPTZ,
device_id INTEGER,
temperature FLOAT8
);
SELECT create_hypertable('conditions', by_range('time'));

请参阅以下关于连续聚合的 JOIN 示例

  • 在单个相等条件上使用 ON 子句的 INNER JOIN

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions
    JOIN devices ON devices.id = conditions.device_id
    GROUP BY bucket, devices.name
    WITH NO DATA;
  • 在单个相等条件上使用 ON 子句的 INNER JOIN,并在 WHERE 子句中添加了进一步的条件

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions
    JOIN devices ON devices.id = conditions.device_id
    WHERE devices.location_id = 'location123'
    GROUP BY bucket, devices.name
    WITH NO DATA;
  • WHERE 子句中指定的单个相等条件上的 INNER JOIN

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions, devices
    WHERE devices.id = conditions.device_id
    GROUP BY bucket, devices.name
    WITH NO DATA;
  • 在多个相等条件上的 INNER JOIN

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions
    JOIN devices ON devices.id = conditions.device_id AND devices.location_id = 'location123'
    GROUP BY bucket, devices.name
    WITH NO DATA;

    TimescaleDB v2.16.x 及更高版本。

  • WHERE 子句中指定的带有单个相等条件的 INNER JOIN 可以与 WHERE 子句中的进一步条件组合使用

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions, devices
    WHERE devices.id = conditions.device_id
    AND devices.location_id = 'location123'
    GROUP BY bucket, devices.name
    WITH NO DATA;

    TimescaleDB v2.16.x 及更高版本。

  • 超表和多个 Postgres 表之间的 INNER JOIN

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name AS device, locations.name AS location, MIN(temperature), MAX(temperature)
    FROM conditions
    JOIN devices ON devices.id = conditions.device_id
    JOIN locations ON locations.id = devices.location_id
    GROUP BY bucket, devices.name, locations.name
    WITH NO DATA;

    TimescaleDB v2.16.x 及更高版本。

  • 超表和 Postgres 表之间的 LEFT JOIN

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions
    LEFT JOIN devices ON devices.id = conditions.device_id
    GROUP BY bucket, devices.name
    WITH NO DATA;

    TimescaleDB v2.16.x 及更高版本。

  • 超表和子查询之间的 LATERAL JOIN

    CREATE MATERIALIZED VIEW conditions_by_day WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 day', time) AS bucket, devices.name, MIN(temperature), MAX(temperature)
    FROM conditions,
    LATERAL (SELECT * FROM devices WHERE devices.id = conditions.device_id) AS devices
    GROUP BY bucket, devices.name
    WITH NO DATA;

    TimescaleDB v2.16.x 及更高版本。

在 TimescaleDB 2.7 及更高版本中,连续聚合支持所有 PostgreSQL 聚合函数。这包括可并行聚合(例如 SUMAVG)和不可并行聚合(例如 RANK)。

在 TimescaleDB 2.10.0 及更高版本中,FROM 子句支持 JOINS,但有一些限制。有关更多信息,请参阅 JOIN 支持部分

在旧版本的 Timescale 中,连续聚合仅支持可以被 PostgreSQL 并行化的聚合函数。您可以通过在连续聚合中聚合查询的其他部分来解决此问题,然后使用窗口函数查询聚合

下表总结了连续聚合中支持的聚合函数

函数、子句或功能TimescaleDB 2.6 及更早版本TimescaleDB 2.7、2.8 和 2.9TimescaleDB 2.10 及更高版本
可并行聚合函数
不可并行 SQL 聚合
ORDER BY
排序集聚合
假设集聚合
聚合函数中的 DISTINCT
聚合函数中的 FILTER
FROM 子句支持 JOINS

DISTINCT 在聚合函数中起作用,但在查询定义中不起作用。例如,对于下表

CREATE TABLE public.candle(
symbol_id uuid NOT NULL,
symbol text NOT NULL,
"time" timestamp with time zone NOT NULL,
open double precision NOT NULL,
high double precision NOT NULL,
low double precision NOT NULL,
close double precision NOT NULL,
volume double precision NOT NULL
);
  • 以下代码有效
    CREATE MATERIALIZED VIEW candles_start_end
    WITH (timescaledb.continuous) AS
    SELECT time_bucket('1 hour', "time"), COUNT(DISTINCT symbol), first(time, time) as first_candle, last(time, time) as last_candle
    FROM candle
    GROUP BY 1;
  • 以下代码无效
    CREATE MATERIALIZED VIEW candles_start_end
    WITH (timescaledb.continuous) AS
    SELECT DISTINCT ON (symbol)
    symbol,symbol_id, first(time, time) as first_candle, last(time, time) as last_candle
    FROM candle
    GROUP BY symbol_id;

如果您希望在更高版本的 TimescaleDB 中使用旧的行为,请在创建连续聚合时将 timescaledb.finalized 参数设置为 false

连续聚合由以下部分组成

  • 用于存储聚合数据的物化超表
  • 用于将原始底层表中的数据聚合到物化超表的物化引擎
  • 用于确定何时需要重新物化数据(由于数据更改)的失效引擎
  • 用于访问聚合数据的查询引擎

连续聚合从原始超表获取原始数据,对其进行聚合,并将中间状态存储在物化超表中。当您查询连续聚合视图时,状态会根据需要返回给您。

使用相同的温度示例,物化表如下所示

日期地点平均温度部分值
2021/01/01纽约1{3, 219}
2021/01/01斯德哥尔摩1{4, 280}
2021/01/02纽约2
2021/01/02斯德哥尔摩2{5, 345}

物化表存储为 Timescale 超表,以利用超表提供的扩展和查询优化。物化表包含查询中每个 group-by 子句的列、一个标识此条目来自原始数据中哪个块的 chunk 列,以及查询中每个聚合的 partial aggregate 列。

partial 列在内部用于计算输出。在本例中,由于查询查找平均值,因此 partial 列包含看到的行数以及所有值的总和。关于 partials 最重要的事情是,它们可以组合以创建跨越所有旧 partials 行的新 partials。如果您组合跨越多个块的组,这将非常重要。

有关更多信息,请参阅物化超表

物化引擎执行两个事务。第一个事务阻止所有 INSERT、UPDATE 和 DELETE,确定要物化的时间范围,并更新失效阈值。第二个事务取消阻止其他事务,并物化聚合。第一个事务非常快,大部分工作发生在第二个事务期间,以确保该工作不会干扰其他操作。

当您查询连续聚合视图时,物化引擎将聚合 partials 组合成每个时间范围的单个 partial,并计算返回的值。例如,要计算平均值,每个 partial 总和加起来得到总和,每个 partial 计数加起来得到总计数,然后将平均值计算为总和除以总计数。

对超表中数据的任何更改都可能使某些物化行失效。失效引擎检查以确保系统不会被失效淹没。

幸运的是,时序数据意味着几乎所有的 INSERT 和 UPDATE 都具有最近的时间戳,因此失效引擎不会物化所有数据,而是物化到称为物化阈值的某个时间点。设置此阈值是为了使绝大多数 INSERT 包含较新的时间戳。这些数据点从未被连续聚合物化,因此无需额外的工作来通知连续聚合已添加它们。当下一次物化器运行时,它负责确定可以在不使连续聚合失效的情况下物化多少新数据。然后,它物化较新的数据,并将物化阈值向前移动。这确保了阈值滞后于数据更改常见的时间点,并且大多数 INSERT 不需要任何额外的写入。

当更改早于失效阈值的数据时,将记录已更改行的最大和最小时间戳,并且这些值用于确定聚合表中哪些行需要重新计算。这种日志记录确实会导致一些写入负载,但是由于阈值滞后于当前正在更改的数据区域,因此写入量很小且很少发生。

关键词

在此页面上发现问题?报告问题 或 在 GitHub 上编辑此页