查看原文
其他

又想 Cube 小,又想 Cube 跑得好?

周天鹏 apachekylin 2022-04-23

“随着维度数目的增加,Cuboid 的数量会爆炸式地增长。为了缓解 Cube 的构建压力,Apache Kylin 引入了一系列的高级设置,帮助用户筛选出真正需要的 Cuboid。这些高级设置包括聚合组(Aggregation Group)、联合维度(Joint Dimension)、层级维度(Hierachy Dimension)和必要维度(Mandatory Dimension)等。”


正如上述官方文档提到的,在维度过多时,合理地使用聚合组能解决 Cube 膨胀率过大的问题。听起来那么美好,但是,不合理的聚合组设置将对性能产生灾难性影响。


剪枝原理

Apache Kylin 的主要工作就是为源数据构建 N 个维度的 Cube,实现聚合的预计算。从理论上说,构建 N 个维度的 Cube 就会生成 2^N 个 Cuboid。


所以,只要降低最终 Cuboid 的数量,就可以减小膨胀率,达到对 Cube 剪枝的效果。


构建一个 4 个维度(A,B,C, D)的 Cube,就需要生成 16 个Cuboid。


那么问题来了,如果这 4 个维度(A,B,C, D),能够根据某业务逻辑找出一个隐藏的规律,即:当进行聚合时,用户仅仅关注维度 AB 组合和维度 CD 组合(即只会通过维度 A 和 B 或者 C 和 D 进行聚合,而不会通过 A 和 C、B 和 C、A 和 D、B 和 D 进行聚合),那么就可以通过设置聚合组,使生成的 Cuboid 数目从 16 个缩减成 8 个(大大降低 Cube 膨胀率),如下图所示。


上面这段内容来自 Kylin 公众号的【技术帖】Apache Kylin 高级设置:聚合组(Aggregation Group)原理解析这篇文章中,值得对聚合组还不太了解的同学读一读。


但是,这里好像完全没有提到用于过滤数据(而不是聚合)的维度字段,应该怎么处理?


问题产生

某年某月某日,某业务人员突然发现某张报表的打开速度极其缓慢,并上报给系统管理人员。随后,通过对该报表产生的 SQL 进行筛查,发现了如下一条嫌疑重大的 SQL 语句,拖慢了整个报表的打开速度。

select "A","B",sum("VALUE")from test_agg_groupwhere "D" = 1group by 1,2;


Kylin 日志信息:

==========================[QUERY]===============================Query Id: 7fe300c2-211c-9429-eebf-b4cc57bfd679SQL: select "A","B",sum("VALUE")from test_agg_groupwhere "D" = 1group by 1,2;User: ADMINSuccess: trueDuration: 4.891Project: 0000_reservedRealization Names: [CUBE[name=test_agg_group]]Cuboid Ids: [15]Total scan count: 1000000Total scan bytes: 51000000Result row count: 100000Accept Partial: trueIs Partial Result: falseHit Exception Cache: falseStorage cache used: falseIs Query Push-Down: falseIs Prepare: falseTrace URL: nullMessage: null==========================[QUERY]===============================

因为这是在测试环境(数据量不大)执行的 SQL,所以执行时间为 4.891 秒,生产环境真实的 SQL 执行时间已超过 40 秒,Total scan count 为千万级。但是问题出现的原理和线上是一样的。


问题定位

对于这种极慢的 SQL,我通常会观察日志信息中的 Total scan count 与 Result row count 数值差异是否巨大。


如果差异极大(例如上述 SQL 的差异已经达到 10 倍),那就意味着该条 SQL 扫描了很多不会被作为最终结果的无用数据。


此时我发现只要删掉那个 where 条件就可以很快的得到响应:

select "A","B",sum("VALUE")from test_agg_groupgroup by 1,2


Kylin 日志信息:

==========================[QUERY]===============================Query Id: 2a9d7422-7268-2805-f1ac-a0fc544602c9SQL: select "A","B",sum("VALUE")from test_agg_groupgroup by 1,2User: ADMINSuccess: trueDuration: 0.628Project: 0000_reservedRealization Names: [CUBE[name=test_agg_group]]Cuboid Ids: [12]Total scan count: 100000Total scan bytes: 4900000Result row count: 100000Accept Partial: trueIs Partial Result: falseHit Exception Cache: falseStorage cache used: falseIs Query Push-Down: falseIs Prepare: falseTrace URL: nullMessage: null==========================[QUERY]===============================


很明显,相比原 SQL,查询的响应时间就提升了好几个数量级。值得注意的是,Total scan count 也从原来的 100w 降到了 10w。


如果是一个传统 RDBMS 的 DBA 看到这一幕,一定会感到疑惑,添加了 where 条件的 SQL 扫描的行数竟然比没有 where 条件的 SQL 扫描的行数更多,简直不可思议。


问题根源

看到这里,有人可能已经逐渐忘记了标题。


回到这个 Cube 上看一看,它教科书般地使用了聚合组进行剪枝操作,完美的将 AB 和 CD 分到了两个聚合组中,将膨胀率降低了一半。


因此,当我们以 AB 维度进行聚合,D 维度进行过滤,Kylin 在搜索哪些行满足 D=1 这个条件时,就无法通过下图的方式进行搜索了。

因为不会有任何一个 Cuboid(大约 10w 行)像上面这样包含 ABD 三个维度和预计算好的值。所以最终 Kylin 会扫描下面这个 Cuboid (即包含 ABCD 四个字段的 Cuboid,大约有 100w 行)来获取最终数据。

这是一个在聚合组设置不当,且运气还很差的情况下才能触发的问题。


运气差在哪?

  1. C 字段的基数非常大

  2. D 字段的基数非常小


通过查看 SQL 执行的日志信息我们也能看到。当以 D 字段为过滤条件时,只能使用包含 ABCD 四个字段的 Cuboid 进行扫描。


但是 C 字段的基数非常大,所以该 Cuboid 的行数也就非常多。同时, C 字段并没有进行筛选,使用了基数非常小的 D 字段进行了筛选(一共 1000w 行,D字段有 500w 行是 1,500w 行是 2)。


最终导致要扫描完 Cuboid ABCD 的 100w 行才能得到计算结果。


那么如果筛选字段不是 D 而是 C,我们尝试下估算下需要扫描多少行呢?

select "A","B",sum("VALUE")from test_agg_groupwhere "C" = 100000group by 1,2


Kylin 日志信息:

==========================[QUERY]===============================Query Id: e304ae37-f7ec-233b-d353-845e2feba908SQL: select "A","B",sum("VALUE")from test_agg_groupwhere "C" = 100000group by 1,2User: ADMINSuccess: trueDuration: 0.806Project: 0000_reservedRealization Names: [CUBE[name=test_agg_group]]Cuboid Ids: [15]Total scan count: 2Total scan bytes: 102Result row count: 2Accept Partial: trueIs Partial Result: falseHit Exception Cache: falseStorage cache used: falseIs Query Push-Down: falseIs Prepare: falseTrace URL: nullMessage: null==========================[QUERY]===============================


仅需要扫描个位数的行即可,因为 C 字段基数大,包含的重复值很少。而且我们可以看到,这条 SQL 和最初的 SQL 都是用了 Cuboid Id 为 15 的 Cuboid 进行查询,也就是包含了 ABCD 四个字段的 Cuboid。


而仅用了 AB 两个字段,不使用 CD 中任何一个字段进行筛选的 SQL 使用了 Cuboid Id 为 12 的 Cuboid。


总结

分聚合组时,哪怕用户仅仅关注维度 AB 组合和维度 CD 组合,但用户会可能用 D 作为过滤条件来查询 AB 组合,就一定要保证 ABD 要分到同一个聚合组当中。


当然了,如果字段的基数不像例子中这么极端,聚合组随便怎么分对性能影响应该都不大。但是,如果哪天墨菲定律突然上线,希望大家能想起本文。


活动报名

9 月 7 日(本周六)深圳

Kylin × RocketMQ Meetup

腾讯、阿里、平安云的技术专家等着你~

↓↓↓扫码报名↓↓↓



往期案例与实践

如何在 Kylin 中优雅地使用 Spark

如何简化 SQL 语句之 UDF 实践

Python + Apache Kylin 让数据分析更加简单!

基于 Apache Kylin 的微博舆情实时分析

解读 Kylin 3.0.0 | 更敏捷、更高效的 OLAP 引擎


"Apache and Apache Kylin are either registered trademarks or trademarks of The Apache Software Foundation in the US and/or other countries. No endorsement by The Apache Software Foundation is implied by the use of these marks."


点“阅读原文”报名深圳 Meetup

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存