核心原理与数据模型:预聚合的基石

预聚合技术,说白了就是「空间换时间」。

你想想看,为什么查询会慢?因为数据太多,计算太复杂。每次用户点个报表,后台都要扫描几亿行数据,做分组、求和、去重——不慢才怪。

那换个思路呢?提前把常用的计算结果算好,存起来。用户查的时候,直接读结果,不用再算一遍。这就是预聚合的核心思想。

我个人习惯把预聚合比作「预制菜」。你点一份鱼香肉丝,如果厨师从洗菜切肉开始做,得等20分钟。但如果肉丝、酱料都是提前备好的,下锅一炒,3分钟上桌。预聚合就是那个「提前备好的半成品」。

核心公式:查询延迟 ≈ 数据量 × 计算复杂度 ÷ 预聚合度

预聚合度越高,查询越快。代价是存储空间和写入开销会增加。

空间换时间:这笔账怎么算?

我在项目中遇到过不少团队,一听到「预聚合」就摇头:「存储不要钱吗?」

嗯,这里要注意。存储确实要钱,但计算资源更贵。一台高配查询节点的价格,够你买好几块SSD了。而且用户等报表加载的时候,耐心可没那么值钱。

我给大家算笔账:

维度 无预聚合 有预聚合
查询延迟 5-30秒 50-200毫秒
存储开销 原始数据 100GB 原始 + 预聚合 ≈ 120-150GB
计算资源 高(每次查询都要全量计算) 低(只读预聚合结果)
用户体验 差(等得心焦) 好(秒开)

你看,多花20%-50%的存储,换来几十倍的查询加速。这笔账,我觉得值。

我的经验:预聚合表的存储开销通常控制在原始数据的1.2-2倍之间。超过2倍,就要检查是不是粒度设计出了问题。

常见数据模型:星型 vs 雪花

聊预聚合,绕不开数据模型。我见过太多团队,模型没选好,预聚合做得再好也白搭。

目前主流就两种:星型模型和雪花模型。说白了,就是「一张大宽表」和「拆成多张小表」的区别。

星型模型

星型模型,中心一张事实表,周围一圈维度表。事实表存度量(比如销售额、订单数),维度表存描述信息(比如时间、地区、商品)。

我刚开始做数仓时,特别喜欢星型模型。为什么?简单。查询的时候,事实表 JOIN 维度表,路径短,性能好。

-- 星型模型示例
-- 事实表:sales_order
order_id | product_id | region_id | date_id | amount
--------+-----------+----------+--------+-------
1001    | P01       | R01      | 20240101| 500.00
1002    | P02       | R01      | 20240101| 320.00

-- 维度表:dim_product
product_id | product_name | category
----------+-------------+---------
P01       | 手机        | 数码
P02       | 笔记本      | 数码

-- 维度表:dim_region
region_id | region_name | city
---------+------------+-------
R01      | 华东        | 上海

预聚合在星型模型上做,非常直接。按维度组合 GROUP BY,把结果存起来就行。

雪花模型

雪花模型是星型的「升级版」。维度表继续拆,比如把「地区」拆成「省-市-区」三级。

这样做的好处是数据冗余少,更新方便。但代价是查询要 JOIN 更多表,路径变长。

-- 雪花模型示例
-- 事实表:sales_order(同上)

-- 维度表:dim_product(同上)

-- 维度表:dim_region(拆成两级)
region_id | region_name | province_id
---------+------------+-----------
R01      | 上海        | PRO01

-- 维度表:dim_province
province_id | province_name
-----------+-------------
PRO01      | 上海市

我个人建议:能用星型就别用雪花。雪花模型虽然省存储,但查询复杂,预聚合的维度组合也会爆炸。我曾经在一个项目里接手过雪花模型,维度表拆了5层,预聚合表建了200多张,维护起来想哭。

对比维度 星型模型 雪花模型
查询性能 高(JOIN少) 低(JOIN多)
存储冗余 高 低
维护复杂度 低 高
预聚合友好度 高 低

预聚合表的粒度设计

这是预聚合最核心的环节。粒度,说白了就是「预聚合到什么程度」。

你想想看,如果粒度太细(比如按分钟聚合),数据量还是很大,查询快不了多少。如果粒度太粗(比如按年聚合),很多查询用不了,白建了。

我一般遵循三个原则:

  1. 按查询频率定粒度:用户最常查什么维度组合,就按什么粒度预聚合。
  2. 按业务周期定粒度:日报、周报、月报,分别对应天、周、月粒度。
  3. 按数据量定粒度:数据量大的表,粒度可以粗一点;数据量小的,可以细一点。

避坑指南:我曾经在一个电商项目中,把所有维度组合都建了预聚合表。结果呢?预聚合表比原始数据还大10倍,ETL跑一次要6小时。后来只保留了Top 20的查询组合,存储降了80%,查询覆盖了95%的场景。

粒度设计还有一个关键点:上卷和下钻。

比如你按「天」做了预聚合,用户想看「月」的数据怎么办?可以上卷:把天的数据SUM起来。但如果用户想看「小时」的数据,下钻就做不到了,因为粒度不够细。

所以,我建议设计粒度时,留一个「最细粒度」的预聚合表。比如按小时聚合,这样上卷到天、周、月都很方便。

-- 粒度设计示例
-- 最细粒度:按小时聚合
CREATE TABLE agg_sales_hourly AS
SELECT date_id, hour, product_id, region_id, SUM(amount) AS total_amount
FROM sales_order
GROUP BY date_id, hour, product_id, region_id;

-- 上卷:按天聚合(从小时表计算)
CREATE TABLE agg_sales_daily AS
SELECT date_id, product_id, region_id, SUM(total_amount) AS total_amount
FROM agg_sales_hourly
GROUP BY date_id, product_id, region_id;

这样做的好处是:预聚合表之间可以「级联」计算,不用每次都扫原始数据。

核心逻辑总览

我把这一章的核心逻辑画了张图,方便你理解:

预聚合核心逻辑 原始数据 空间换时间:提前计算 数据模型选择 星型模型 雪花模型 粒度设计 粒度设计 核心逻辑:原始数据 → 空间换时间 → 选择数据模型 → 设计预聚合粒度

这张图把本章的核心逻辑串起来了。从原始数据出发,经过「空间换时间」的预处理,选择合适的数据模型(星型或雪花),最后落到粒度设计上。每一步都影响最终效果。

我的建议:刚开始做预聚合,别贪多。先选一个最常用的查询场景,按星型模型设计,粒度从粗到细逐步调整。跑通了再扩展。