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 四种策略怎么选?一张图说清楚

下面这张图是我自己总结的决策流程,帮你快速定位该用哪种策略:

预聚合策略决策流程图 预聚合策略决策流程 数据是否需要预聚合? 数据量 < 1000 万行?维度组合 < 5? 是 全量预聚合 否 需要实时性? (延迟 < 1 分钟) 是 时间窗口预聚合 否 需要多种时间粒度? 是 分层预聚合 否 增量预聚合 💡 实际项目中,这四种策略经常组合使用 例如:分钟级用时间窗口 + 小时级用增量聚合 + 天级用全量聚合

3.6 组合策略才是王道

看到这里你可能会问:「那我到底用哪种?」

我的答案是:别单选,要组合。

举个例子,我最近帮一个电商平台设计的架构是这样的:

  • 实时大屏:用时间窗口预聚合(滑动窗口,1 分钟粒度),延迟控制在 10 秒以内。
  • 运营看板:用增量预聚合(每 5 分钟跑一次),数据保留 90 天。
  • 财务对账:用全量预聚合(每天凌晨跑一次),数据永久保留。
  • 历史趋势分析:用分层预聚合(分钟级保留 7 天,小时级保留 90 天,天级永久)。

你看,同一个业务系统,不同场景用了不同的策略。这才是预聚合的正确打开方式。

核心原则:没有最好的策略,只有最合适的策略。根据数据量、实时性要求、存储成本、查询模式来选。如果拿不准,先从「增量 + 分层」开始,这是最稳妥的组合。