预聚合模型设计四大原则
预聚合模型设计,说白了就是「用空间换时间」。但怎么换、换多少、换哪些,这里头门道不少。我做了这么多年数仓和 OLAP 系统,踩过的坑能写满一个笔记本。今天把这四个核心原则掰开揉碎讲给你听。
核心原则一览:高基数维度谨慎处理、低基数维度优先聚合、常用查询路径全覆盖、数据倾斜必须规避。这四条缺一不可,顺序也别搞反。
原则一:高基数维度处理
高基数维度,就是那种取值特别多的字段。比如用户ID、订单号、设备MAC地址。我见过有人把用户ID直接作为预聚合的维度,结果聚合表比原始表还大,查询反而更慢。为什么会这样?因为预聚合的核心是「减少数据量」,高基数维度几乎不产生任何压缩效果。
我个人习惯的做法是:先判断维度基数。如果超过10万,基本就不适合直接做预聚合维度。那怎么办?
- 分层聚合:把高基数维度先映射到低基数维度。比如用户ID → 用户等级、地域、年龄段。
- 降维处理:用范围或类别替代精确值。比如设备MAC → 设备厂商、设备类型。
- 保留明细:高基数维度的查询走原始表,预聚合只服务低基数场景。
我的经验:曾经有个项目,用户ID维度基数2000万,硬要做预聚合。结果聚合表每天新增1.2亿行,查询延迟反而从200ms涨到3秒。后来改成按「用户等级+地域」聚合,数据量降到每天50万行,查询回到150ms。高基数维度,该放手时就放手。
原则二:低基数维度优先
低基数维度,说白了就是枚举值很少的字段。比如性别(男/女)、状态(成功/失败/处理中)、星期几(1-7)。这类维度做预聚合,效果立竿见影。
你想想看,一个只有3个取值的维度,预聚合后最多3行数据。就算加上其他低基数维度组合,数据量也是可控的。我建议的优先级排序:
| 维度类型 | 基数范围 | 预聚合优先级 | 典型例子 |
|---|---|---|---|
| 超低基数 | < 100 | 最高 | 性别、状态码、星期 |
| 低基数 | 100 - 1万 | 高 | 城市、商品类目、渠道 |
| 中基数 | 1万 - 10万 | 中 | 商品ID、店铺ID |
| 高基数 | > 10万 | 低/不聚合 | 用户ID、订单号、IP |
嗯,这里要注意:低基数维度之间组合时,数据量是乘积关系。比如「城市(100) × 渠道(5) × 状态(3) = 1500」,完全没问题。但如果再加一个中基数维度「商品ID(5万)」,那就变成7500万了,这时候就得重新评估。
原则三:常用查询路径覆盖
这个原则听起来简单,做起来最难。我见过太多团队,上来就把所有维度组合都建了预聚合表,结果存储涨了10倍,查询却没快多少。为什么?因为80%的查询只用了20%的维度组合。
我的做法是:先跑一周的查询日志,分析出Top-N的查询模式。然后只对这些路径建预聚合。
查询路径分析示例:
-- 假设这是你的查询日志分析结果
-- Top 5 查询模式(按频率排序)
1. SELECT 日期, 城市, SUM(金额) FROM 订单 GROUP BY 日期, 城市
→ 占比 35%,覆盖!
2. SELECT 日期, 渠道, COUNT(*) FROM 订单 GROUP BY 日期, 渠道
→ 占比 22%,覆盖!
3. SELECT 日期, 城市, 渠道, SUM(金额) FROM 订单 GROUP BY 日期, 城市, 渠道
→ 占比 15%,覆盖!
4. SELECT 用户ID, SUM(金额) FROM 订单 GROUP BY 用户ID
→ 占比 8%,高基数,不预聚合,走原始表
5. SELECT 日期, 商品类目, AVG(价格) FROM 订单 GROUP BY 日期, 商品类目
→ 占比 5%,覆盖!
你看,前5个查询模式占了85%的查询量。我们只需要建3张预聚合表,就能覆盖72%的查询。剩下的走原始表,或者用物化视图兜底。这就是「二八原则」在预聚合中的应用。
我曾经踩过的坑:有个项目,我凭经验觉得「日期+城市+渠道+商品类目」这个组合肯定常用,就建了预聚合表。结果跑了两个月,这个表只被查询了3次。后来一查日志,业务方根本不用商品类目做分析。所以,别猜,看日志!
原则四:数据倾斜规避
数据倾斜,说白了就是某些维度的值分布极不均匀。比如「城市」维度,北京、上海的数据量可能是其他城市的100倍。如果不处理,预聚合的时候,北京这个分片就会成为瓶颈,其他节点都在等它。
我遇到过最夸张的情况:一个电商订单表,某个爆款商品的订单量占了总量的60%。预聚合按「商品ID」聚合时,那个商品的分片跑了40分钟,其他商品几秒就完了。整个查询超时。
怎么解决?几个常用手段:
- 热点拆分:把热点值单独拆分出来,做二次聚合。比如北京的数据单独建一个预聚合表,其他城市共用一张。
- 加盐处理:给热点维度值加随机后缀,打散到不同分片。查询时再合并结果。
- 双层聚合:第一层按原始维度聚合,第二层把热点值做子聚合。类似MapReduce的Combine阶段。
我的经验:处理数据倾斜,我一般先跑一个数据分布统计脚本。看看每个维度值的占比,如果某个值超过总量的10%,就标记为热点。然后根据业务场景选择拆分或加盐。记住,倾斜不是bug,是数据的天性,我们要做的是「管理」它,而不是「消灭」它。
四个原则的协同关系
这四个原则不是孤立的。我习惯把它们串起来用:
- 先分析查询日志,确定常用查询路径(原则三)
- 对路径中的维度做基数评估,低基数优先(原则二),高基数降维或跳过(原则一)
- 建表前检查数据分布,发现倾斜就做拆分或加盐(原则四)
- 上线后持续监控,根据查询模式变化调整预聚合策略
说白了,预聚合模型设计就是一个「取舍」的过程。没有银弹,只有根据你的数据特征和查询模式,找到那个最优解。我做了这么多年,也不敢说每次都能一次设计对。但只要你把这四个原则刻在脑子里,至少不会出大错。