3、预聚合策略分类:全量预聚合、增量预聚合、分层预聚合(分钟级/小时级/天级)、基于时间窗口的预聚合
预聚合怎么做?选哪种策略?
说实话,这是我在项目中遇到最多的问题。很多同学一上来就问:「我该用哪种预聚合?」其实没有银弹。不同的场景、不同的数据量、不同的延迟要求,决定了你该走哪条路。
我个人习惯把预聚合策略分成四类:全量、增量、分层、时间窗口。它们不是互斥的,很多时候是组合使用的。今天我们就一个一个拆开来看。
3.1 全量预聚合:简单粗暴,但别滥用
全量预聚合,说白了就是「把历史所有数据重新算一遍」。比如你有一个订单表,每天凌晨跑一个任务,把从开天辟地到昨天的所有订单按天汇总,写入结果表。
适用场景:数据量可控(千万级以内)、维度组合固定、对实时性要求不高。
我在项目中遇到过这样一个案例:一个电商报表系统,每天凌晨 3 点跑全量聚合,把过去 5 年的订单数据按「日期+品类+区域」汇总。数据量大概 2000 万行,跑一次 15 分钟。业务方早上 8 点看报表,完全来得及。
但全量有个大坑——数据膨胀。你想想看,如果维度组合很多(比如 10 个维度),全量预聚合的结果表可能比原始表还大。我曾经见过一个团队,全量聚合后数据膨胀了 20 倍,查询是快了,但存储成本翻了好几番。
避坑指南:全量预聚合只适合维度组合有限(一般不超过 5 个维度)、数据量增长缓慢的场景。如果每天新增几百万行,全量聚合很快会变成「全量噩梦」。
3.2 增量预聚合:只算新数据,效率翻倍
增量预聚合的思路很简单:只处理新增或变化的数据。比如每 5 分钟拉取最近 5 分钟的新订单,聚合后合并到昨天的结果中。
这样做的好处很明显——计算量小、延迟低。但坏处呢?嗯,这里要注意:数据一致性。如果上游数据有回溯修改(比如订单状态从「待支付」变成「已支付」),增量聚合可能漏掉这些变化。
我的经验:增量预聚合一定要配合「数据补偿机制」。我习惯的做法是:每天凌晨跑一次全量校验,对比增量结果和全量结果,发现差异就触发重算。这叫「增量为主,全量兜底」。
来看一个简单的增量聚合伪代码:
-- 每 5 分钟执行一次
INSERT INTO order_daily_summary
SELECT
order_date,
category_id,
region_id,
COUNT(*) AS order_count,
SUM(amount) AS total_amount
FROM orders
WHERE created_at >= NOW() - INTERVAL '5 minutes'
AND created_at < NOW()
GROUP BY order_date, category_id, region_id
ON CONFLICT (order_date, category_id, region_id)
DO UPDATE SET
order_count = order_daily_summary.order_count + EXCLUDED.order_count,
total_amount = order_daily_summary.total_amount + EXCLUDED.total_amount;
这段代码用了 ON CONFLICT ... DO UPDATE,也就是「有则更新,无则插入」。这是增量聚合的经典写法。
3.3 分层预聚合:分钟级/小时级/天级
分层预聚合是我个人最推荐的方式。它的核心思想是:不同时间粒度,用不同的预聚合策略。
举个例子:
- 分钟级:实时流处理,延迟控制在 1 分钟以内,用于实时大屏、监控告警。
- 小时级:每整点聚合一次,用于运营看板、趋势分析。
- 天级:每天凌晨跑一次全量或增量聚合,用于财务对账、月度报告。
为什么要分层?说白了就是用空间换时间,用精度换速度。分钟级数据保留最近 7 天,小时级保留 90 天,天级保留永久。查询时,根据时间范围自动选择最合适的粒度。
关键设计原则:上层(天级)的数据必须能由下层(小时级)汇总得到,反之不一定成立。这叫「可下钻不可上卷」。
我曾经帮一个金融客户设计过分层预聚合架构。他们的交易数据每天 2 亿条,原始表查询要 30 秒以上。分层后:
| 粒度 | 保留周期 | 数据量 | 查询延迟 |
|---|---|---|---|
| 分钟级 | 7 天 | 约 500 万行 | < 100ms |
| 小时级 | 90 天 | 约 200 万行 | < 50ms |
| 天级 | 永久 | 约 10 万行 | < 20ms |
你看,分层之后,不管查哪个时间范围,延迟都在 100ms 以内。这就是分层预聚合的魅力。
3.4 基于时间窗口的预聚合:滑动窗口与滚动窗口
时间窗口预聚合,是流处理场景下的标配。它有两种常见形态:
- 滚动窗口(Tumbling Window):固定时间长度,不重叠。比如每 5 分钟一个窗口,窗口之间没有重叠。
- 滑动窗口(Sliding Window):固定时间长度,但按固定步长滑动。比如窗口长度 10 分钟,每 1 分钟滑动一次,窗口之间有重叠。
我个人的经验是:滚动窗口适合报表统计,滑动窗口适合实时监控。为什么?
滚动窗口的结果是确定的,每个数据只属于一个窗口,适合做「今天 vs 昨天」的对比。滑动窗口则能更平滑地反映趋势变化,但计算量更大,因为每个数据可能属于多个窗口。
避坑指南:滑动窗口的「窗口数」= 窗口长度 / 滑动步长。如果窗口长度 1 小时,步长 1 分钟,那就有 60 个窗口同时存在。每个数据要写入 60 个窗口的结果中。我曾经见过一个团队把步长设得太小,结果预聚合的计算量比原始查询还大——这就本末倒置了。
来看一个 Flink SQL 的滚动窗口示例:
-- 滚动窗口:每 5 分钟聚合一次
SELECT
TUMBLE_START(event_time, INTERVAL '5' MINUTE) AS window_start,
TUMBLE_END(event_time, INTERVAL '5' MINUTE) AS window_end,
category_id,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM orders
GROUP BY
TUMBLE(event_time, INTERVAL '5' MINUTE),
category_id;
滑动窗口的写法也类似,只是把 TUMBLE 换成 HOP:
-- 滑动窗口:窗口长度 10 分钟,每 1 分钟滑动一次
SELECT
HOP_START(event_time, INTERVAL '1' MINUTE, INTERVAL '10' MINUTE) AS window_start,
HOP_END(event_time, INTERVAL '1' MINUTE, INTERVAL '10' MINUTE) AS window_end,
category_id,
COUNT(*) AS cnt,
SUM(amount) AS total
FROM orders
GROUP BY
HOP(event_time, INTERVAL '1' MINUTE, INTERVAL '10' MINUTE),
category_id;
3.5 四种策略怎么选?一张图说清楚
下面这张图是我自己总结的决策流程,帮你快速定位该用哪种策略:
3.6 组合策略才是王道
看到这里你可能会问:「那我到底用哪种?」
我的答案是:别单选,要组合。
举个例子,我最近帮一个电商平台设计的架构是这样的:
- 实时大屏:用时间窗口预聚合(滑动窗口,1 分钟粒度),延迟控制在 10 秒以内。
- 运营看板:用增量预聚合(每 5 分钟跑一次),数据保留 90 天。
- 财务对账:用全量预聚合(每天凌晨跑一次),数据永久保留。
- 历史趋势分析:用分层预聚合(分钟级保留 7 天,小时级保留 90 天,天级永久)。
你看,同一个业务系统,不同场景用了不同的策略。这才是预聚合的正确打开方式。
核心原则:没有最好的策略,只有最合适的策略。根据数据量、实时性要求、存储成本、查询模式来选。如果拿不准,先从「增量 + 分层」开始,这是最稳妥的组合。