使用物化视图构建汇总表,实现快速时间序列分析
本教程将演示如何使用 materialized views 从高吞吐量事件表维护预聚合的汇总数据。 您将创建三个对象:一个原始表、一个汇总表,以及一个会自动将数据写入该汇总表的物化视图。
何时使用此模式
在以下情况下使用此模式:
- 你有一个仅追加写入的事件流(点击、页面浏览、IoT、日志)。
- 大多数查询是在时间范围上的聚合(按分钟/小时/天)。
- 你希望获得稳定的一致的亚秒级读取性能,且无需重新扫描所有原始行。
创建原始事件表
注意事项
PARTITION BY toYYYYMM(event_time)可以使分区保持较小,便于删除。ORDER BY (event_time, user_id)支持时间范围查询 + 二级过滤条件。LowCardinality(String)能为类别型维度节省内存。TTL会在 90 天后清理原始数据(可根据保留需求进行调整)。
设计汇总(聚合)表
我们将预聚合到小时粒度。 请根据最常用的分析时间窗口选择合适的粒度。
我们存储聚合状态(例如 AggregateFunction(sum, ...))来紧凑地表示部分聚合,这些状态可以在后续合并或完成最终计算。
查询汇总表
您可以在读取时合并状态,或者完成它们:
- 在读取时合并
- 使用 -Final 进行最终合并
提示
如果您预期读取操作始终命中汇总数据,可以创建第二个物化视图,将_最终确定的_数值写入"普通"的 MergeTree 表,保持相同的 1 小时粒度。
状态提供了更大的灵活性,而最终确定的数值则使读取操作更加简洁。
在主键字段上过滤以获得最佳性能
您可以使用 EXPLAIN 命令查看索引如何用于裁剪数据:
上述查询执行计划显示使用了三种类型的索引:
MinMax 索引、分区索引和主键索引。
每个索引都使用了主键中指定的字段:(bucket_start, country, event_type)。
为获得最佳过滤性能,请确保您的查询使用主键字段来裁剪数据。
常见配置变体
- 不同粒度:添加一个按日汇总:
然后创建第二个物化视图:
- 压缩:在原始表中的大列上应用压缩编解码器(例如:
Codec(ZSTD(3)))。 - 成本控制:将长保留策略应用于原始表,并保留生命周期较长的汇总表。
- Backfilling(回填):在加载历史数据时,将数据插入到
events_raw中,让物化视图自动完成汇总构建。对于已有行,如果适用,可以在创建物化视图时使用POPULATE,或使用INSERT SELECT。
清理与保留策略
- 延长原始数据的 TTL(例如 30/90 天),但将汇总数据的保留时间设得更长(例如 1 年)。
- 如果启用了分层存储,你也可以使用 TTL 将 旧的数据分片迁移到更便宜的存储中。
故障排除
- 物化视图没有更新?检查插入是否写入到 events_raw(而不是汇总表),并确认物化视图的目标表是否正确(
TO events_rollup_1h)。 - 查询变慢?请确认这些查询确实命中了 rollup(直接查询 rollup 表),并确保时间过滤条件与 rollup 表的汇总粒度对齐。
- 回填数据有不一致?使用
SYSTEM FLUSH LOGS,并检查system.query_log和system.parts,以确认插入和合并是否正确完成。