核心原理与数据模型:预聚合的基石
预聚合技术,说白了就是「空间换时间」。
你想想看,为什么查询会慢?因为数据太多,计算太复杂。每次用户点个报表,后台都要扫描几亿行数据,做分组、求和、去重——不慢才怪。
那换个思路呢?提前把常用的计算结果算好,存起来。用户查的时候,直接读结果,不用再算一遍。这就是预聚合的核心思想。
我个人习惯把预聚合比作「预制菜」。你点一份鱼香肉丝,如果厨师从洗菜切肉开始做,得等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多) |
| 存储冗余 | 高 | 低 |
| 维护复杂度 | 低 | 高 |
| 预聚合友好度 | 高 | 低 |
预聚合表的粒度设计
这是预聚合最核心的环节。粒度,说白了就是「预聚合到什么程度」。
你想想看,如果粒度太细(比如按分钟聚合),数据量还是很大,查询快不了多少。如果粒度太粗(比如按年聚合),很多查询用不了,白建了。
我一般遵循三个原则:
- 按查询频率定粒度:用户最常查什么维度组合,就按什么粒度预聚合。
- 按业务周期定粒度:日报、周报、月报,分别对应天、周、月粒度。
- 按数据量定粒度:数据量大的表,粒度可以粗一点;数据量小的,可以细一点。
避坑指南:我曾经在一个电商项目中,把所有维度组合都建了预聚合表。结果呢?预聚合表比原始数据还大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;
这样做的好处是:预聚合表之间可以「级联」计算,不用每次都扫原始数据。
核心逻辑总览
我把这一章的核心逻辑画了张图,方便你理解:
这张图把本章的核心逻辑串起来了。从原始数据出发,经过「空间换时间」的预处理,选择合适的数据模型(星型或雪花),最后落到粒度设计上。每一步都影响最终效果。
我的建议:刚开始做预聚合,别贪多。先选一个最常用的查询场景,按星型模型设计,粒度从粗到细逐步调整。跑通了再扩展。