TimescaleDB Continuous Aggregates:实时 vs 仅物化
Source: Dev.to
TimescaleDB 连续聚合:实时 vs 仅物化
在 TimescaleDB 中,连续聚合(Continuous Aggregates) 让我们能够在大规模时间序列数据上执行近实时的汇总查询,而无需每次都扫描原始数据。本文将比较两种常见的使用模式:
- 实时(real‑time)模式 – 通过
WITH子句或refresh_lag参数让查询在最近的分区上直接读取原始行。 - 仅物化(materialized‑only)模式 – 完全依赖已经预先计算好的物化视图(materialized view),不查询底层 hypertable。
我们将通过示例、性能基准以及最佳实践来说明何时选择哪种方式。
目录
连续聚合的工作原理
TimescaleDB 在后台维护一个 物化视图(materialized view),该视图定期刷新(默认每 1 小时),把旧的分区数据聚合成更紧凑的结果。对于最近的分区(通常是最近的几天),系统会保留 实时窗口,在查询时直接读取原始行,以确保结果包含最新数据。
关键概念:
| 概念 | 说明 |
|---|---|
refresh_lag | 指定视图在刷新前“落后”多少时间。例如 refresh_lag = '5 minutes' 表示最近 5 分钟的数据不在物化视图中,而是实时查询。 |
max_interval_per_job | 控制每次后台刷新任务处理的时间范围,防止一次性刷新导致性能抖动。 |
continuous_aggregate | 使用 CREATE MATERIALIZED VIEW ... WITH (timescaledb.continuous) AS ... 创建的视图。 |
实时模式示例
下面的示例创建一个每分钟聚合的连续视图,并将 refresh_lag 设置为 2 分钟,这样最近 2 分钟的数据会实时返回。
CREATE MATERIALIZED VIEW cpu_usage_hourly
WITH (timescaledb.continuous, timescaledb.refresh_lag = '2 minutes')
AS
SELECT
time_bucket('1 hour', ts) AS bucket,
device_id,
AVG(cpu_percent) AS avg_cpu,
MAX(cpu_percent) AS max_cpu
FROM metrics
GROUP BY bucket, device_id;
查询时,TimescaleDB 会自动合并:
SELECT *
FROM cpu_usage_hourly
WHERE bucket >= now() - interval '24 hours';
- 旧数据(> 2 分钟)来自物化视图,查询速度极快。
- 新数据(≤ 2 分钟)直接从
metrics表读取,确保结果实时。
如果想手动强制实时查询,可以使用 WITH 子句:
SELECT *
FROM cpu_usage_hourly
WHERE bucket >= now() - interval '5 minutes'
WITH (timescaledb.continuous_aggregate_real_time = true);
仅物化模式示例
在某些场景下(例如报表或离线分析),我们不需要最新的几秒钟数据,只关心已经物化的结果。此时可以把 refresh_lag 设为 0,或者在查询时显式关闭实时窗口。
CREATE MATERIALIZED VIEW temperature_daily
WITH (timescaledb.continuous, timescaledb.refresh_lag = '0')
AS
SELECT
time_bucket('1 day', ts) AS day,
location,
AVG(temp_c) AS avg_temp
FROM weather
GROUP BY day, location;
查询:
SELECT *
FROM temperature_daily
WHERE day >= now() - interval '30 days';
因为 refresh_lag = '0',所有数据都已经在视图中,查询仅访问物化视图,性能最佳。
如果视图已经创建且想在查询时关闭实时功能:
SELECT *
FROM temperature_daily
WHERE day >= now() - interval '30 days'
WITH (timescaledb.continuous_aggregate_real_time = false);
性能对比
| 场景 | 查询时间(示例) | I/O 读取 | 备注 |
|---|---|---|---|
| 实时模式(最近 5 分钟) | 120 ms | 读取原始分区 + 物化视图 | 包含实时行,略高于仅物化 |
| 仅物化(全部) | 45 ms | 只读取物化视图 | 最快,但不包含最新数据 |
| 全表扫描(无聚合) | 1.8 s | 读取完整 hypertable | 仅作对照 |
实验环境:PostgreSQL 15 + TimescaleDB 2.11,8 CPU,32 GB RAM,SSD 存储。数据量约 1.2 B 行(约 500 GB)。
从表中可以看到,仅物化模式在大多数报表查询中提供了显著的性能优势,而 实时模式 只在需要最新几分钟数据的监控仪表盘中才值得使用。
何时使用实时模式?
| 使用场景 | 推荐设置 |
|---|---|
| 监控仪表盘,需要 秒级 或 分钟级 的最新指标 | refresh_lag 设为 1 minute 或 30 seconds,并在查询中启用 continuous_aggregate_real_time = true |
| 警报系统,需要在阈值触发时立即捕获 | 同上,且可以结合 WHERE ts > now() - interval '1 minute' |
| 大规模离线分析或月度报表 | 关闭实时(refresh_lag = '0' 或 continuous_aggregate_real_time = false) |
最佳实践:
- 分层聚合:先创建细粒度(如 1 分钟)的连续视图,再基于它创建更粗的视图(如 1 小时),这样可以在不同层次上灵活控制
refresh_lag。 - 监控刷新任务:使用
timescaledb_information.continuous_aggregates视图查看每个聚合的刷新状态,确保后台任务没有积压。 - 避免过小的
refresh_lag:如果设置为几秒钟,后台刷新频率会急剧上升,可能导致写入放大(write amplification)和资源竞争。
结论
- 实时模式 为需要最新数据的交互式仪表盘提供了便利,但会带来额外的 I/O 开销。
- 仅物化模式 在大多数批量查询和报表场景下是最优选择,能够最大化查询性能并最小化系统负载。
- 通过合理配置
refresh_lag、分层聚合以及监控后台刷新任务,能够在 实时性 与 性能 之间取得平衡。
希望本文能帮助你在实际项目中更好地利用 TimescaleDB 的连续聚合特性。如果还有其他疑问,欢迎在评论区交流!
Source: …
什么是连续聚合?
连续聚合是由其自身隐藏的 hypertable 支持的物化视图。TimescaleDB 将预先计算好的聚合结果存储在这个物化 hypertable 中,因此查询读取的是紧凑的汇总行,而不是扫描数百万条原始记录。
CREATE MATERIALIZED VIEW hourly_device_metrics
WITH (timescaledb.continuous) AS
SELECT
time_bucket('1 hour', event_timestamp_utc) AS bucket_hour_utc,
device_id,
AVG(metric_value) AS avg_metric_value,
MAX(metric_value) AS max_metric_value,
COUNT(*) AS event_count
FROM sensor_events
GROUP BY bucket_hour_utc, device_id;
创建 CAGG 定义了它的结构 但不会填充数据。物化 hypertable 在你添加刷新策略或手动执行刷新之前保持为空。
刷新策略
刷新策略告诉 TimescaleDB 定期为滑动时间窗口重新计算聚合:
SELECT add_continuous_aggregate_policy(
'hourly_device_metrics',
start_offset => INTERVAL '3 hours',
end_offset => INTERVAL '1 hour',
schedule_interval => INTERVAL '30 minutes'
);
三个参数如何交互
| 参数 | 含义 |
|---|---|
start_offset | 从 now() 向回多远,刷新窗口 开始 的位置。使用 3 hours 时,策略会从 3 小时前开始重新物化数据。 |
end_offset | 从 now() 向回多远,刷新窗口 结束 的位置。使用 1 hour 时,最近 1 小时内的数据 永不 被该策略物化。 |
schedule_interval | 策略运行的频率。 |
end_offset 是关键参数。 它刻意留下一个间隙:最近的数据窗口会被策略有意跳过。这 不是 bug。最近的时间桶仍在持续累积数据。若对一个尚未填满的桶进行物化,然后几分钟后再次物化,会导致计算浪费。end_offset 可以防止这种 churn(无效的重复计算)。
实际后果: 使用上述配置时,物化的数据始终至少 1 小时 旧,最多可能 1.5 小时 旧(end_offset 加上一次 schedule_interval 周期的时间)。
实时模式(默认)
当您查询实时 CAGG 时,TimescaleDB 会透明地组合两个数据源:
- 预计算结果 来自历史范围的物化 hypertable。
- 实时聚合 针对任何新于物化水印的数据,在源 hypertable 上进行。
这两个结果集会 自动合并。您的应用程序看到的是一个完整的单一结果集,就好像所有数据都已经物化一样。
您可以在查询计划中看到:一个带有两个子节点的 Append 节点——一个扫描物化 hypertable(快速),另一个扫描源 hypertable(较慢,因为它在原始数据上执行完整聚合)。
权衡
| 方面 | 影响 |
|---|---|
| 历史部分 | 快速(读取预计算行) |
| 近期部分 | 较慢(实时聚合原始数据) |
| 24 小时仪表板的整体延迟 | 前 23 小时快速,最后一小时较慢 |
更大的 end_offset 或自上次刷新以来时间更长的影响 | 更多数据通过较慢的实时路径 |
仅物化模式
您可以完全禁用实时联合:
ALTER MATERIALIZED VIEW hourly_device_metrics
SET (timescaledb.materialized_only = true);
在 仅物化模式 下,CAGG 只返回 预先计算 的数据。查询永不触及源 hypertable。最近的数据(end_offset 窗口内的所有数据)根本 不会出现。
优势
- 查询更快且更可预测;没有实时聚合路径,也不会因未物化数据的累积量而导致性能波动。
- 查询计划显示对物化 hypertable 的 单次扫描。
您可以随时在两种模式之间切换,且不会丢失数据:
-- 切换到仅物化(查询更快,最近窗口数据陈旧)
ALTER MATERIALIZED VIEW hourly_device_metrics
SET (timescaledb.materialized_only = true);
-- 切换回实时(最近窗口查询较慢,数据完整)
ALTER MATERIALIZED VIEW hourly_device_metrics
SET (timescaledb.materialized_only = false);
不会丢弃或重新计算任何数据;切换仅改变查询执行器是否附加实时联合。
Comparison Table
| Aspect | Real‑Time (default) | Materialized‑Only |
|---|---|---|
| Data freshness | 当前(截至目前) | 过期,取决于 end_offset + schedule_interval |
| Recent query performance | 较慢(实时聚合) | 快速(仅物化) |
| Historical query performance | 相同 | 相同 |
| Touches source hypertable | 是,仅针对未物化的范围 | 从不 |
| Best for | 仪表板、警报、运营监控 | 报告、计费、分析、批处理流水线 |
何时使用哪种模式
- 实时模式 – 当消费者需要最新的分钟级数据且能够容忍最近窗口稍高的延迟时使用。适用于运营仪表盘和告警。
- 仅物化模式 – 当对新鲜度要求放宽且查询一致性更重要时使用。适用于计费计算、每日报告以及按计划运行且不需要实时聚合开销的分析管道。
静默失败模式
连续聚合的静默失败模式是 陈旧。在仅物化模式下,CAGG 返回的结果会因 end_offset 加上 schedule_interval 的大小而 陈旧。务必选择符合应用新鲜度需求的模式。
-- Check the materialization watermark and detect staleness
SELECT
view_name,
materialization_hypertable_name,
(SELECT max(bucket_hour_utc) FROM hourly_device_metrics) AS latest_materialized,
now() - (SELECT max(bucket_hour_utc) FROM hourly_device_metrics) AS staleness
FROM timescaledb_information.continuous_aggregates
WHERE view_name = 'hourly_device_metrics';
如果 陈旧度 超过 end_offset 加 schedule_interval 的显著幅度,则刷新策略未正常运行。检查 timescaledb_information.job_stats 中刷新任务的 last_successful_finish 和 total_failures。
注意:
刷新策略中的end_offset不是 bug 或错误配置。它是有意的设计选择,用于防止对部分填充的桶进行无效的重新计算。
- 实时模式 透明地填补空白,确保结果是最新的。
- 仅物化模式 在速度比新鲜度更重要时很有用。
推荐
- 从实时模式开始。
- 仅在确定了特定查询或使用场景且实时聚合开销不必要时,才切换到仅物化模式。
模式之间的切换是即时且可逆的。了解每个连续聚合运行的模式以及原因,可确保聚合数据的可信度。