本教程将向你展示如何使用 materialized views 基于海量事件表维护预聚合 rollup。 你将创建三个对象:原始表、rollup 表,以及会自动将数据写入 rollup 的 materialized view。
何时使用此模式
- 你有一个仅追加的事件流 (点击、页面浏览、IoT、日志) 。
- 大多数查询都是针对时间范围的聚合 (按分钟/小时/天) 。
- 你希望获得稳定的亚秒级读取性能,而无需重新扫描所有原始行。
1
创建原始事件表
PARTITION BY toYYYYMM(event_time)可使分区保持较小,便于删除。ORDER BY (event_time, user_id)支持带时间范围限制的查询以及次级过滤条件。LowCardinality(String)可为分类维度节省内存。TTL会在 90 天后清理原始数据 (可根据保留需求调整) 。
2
设计 rollup (聚合) 表
我们将按小时粒度进行预聚合。
选择的粒度应与最常见的分析时间窗口相匹配。我们存储聚合状态 (例如
AggregateFunction(sum, ...)) ,它们以紧凑形式表示部分聚合,后续可再进行合并或最终计算。3
创建一个用于填充 rollup 的 materialized view
这个 materialized view 会在向
events_raw 插入数据时自动触发,并将聚合状态写入 rollup。4
插入一些样例数据
插入一些样例数据:
5
查询 rollup
你既可以在读取时合并状态,也可以对其进行最终计算:
- 在读取时合并
- 使用 -Final 完成最终计算
6
按主键字段过滤可获得最佳性能
你可以使用 上面的查询执行计划显示使用了三种类型的索引:
MinMax 索引、分区索引和主键索引。
每个索引都会用到我们在主键中指定的字段:
EXPLAIN 命令来查看索引如何用于减少读取的数据:Query
Response
(bucket_start, country, event_type)。
为了获得最佳过滤性能,你需要确保查询会利用主键字段来裁剪数据。7
常见变体
- 不同粒度:添加按日 rollup:
- 压缩:在原始表中为大列应用编解码器 (例如:
Codec(ZSTD(3))) 。 - 成本控制:将较重的保留负载放在原始表中,并长期保留汇总数据。
- 历史回填:加载历史数据时,将数据插入
events_raw,让 materialized view 自动构建汇总。对于现有行,如果适用,可在创建 materialized view 时使用POPULATE,或使用INSERT SELECT。
8
清理与保留
- 延长原始数据的生存时间 (TTL) (例如 30/90 天) ,但让汇总数据保留更久 (例如 1 年) 。
- 如果已启用分层存储,你还可以使用生存时间 (TTL) 将旧 parts 迁移到成本更低的存储。
9
故障排查
- materialized view 没有更新?请检查插入是否写入 events_raw (而不是 rollup 表) ,并确认 materialized view 的目标是否正确 (
TO events_rollup_1h) 。 - 查询很慢?请确认查询命中了 rollup (直接查询 rollup 表) ,并且时间过滤条件与 rollup 粒度一致。
- 回填结果不一致?请使用
SYSTEM FLUSH LOGS,并检查system.query_log/system.parts以确认插入和合并是否正常。