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

笑着哭i 提交于 2019-11-29 10:30:28

“随着维度数目的增加,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_group  where "D" = 1  group by 1,2;

Kylin 日志信息:

  ==========================[QUERY]===============================  Query Id: 7fe300c2-211c-9429-eebf-b4cc57bfd679  SQL: select "A","B",sum("VALUE")  from test_agg_group  where "D" = 1  group by 1,2;  User: ADMIN  Success: true  Duration: 4.891  Project: 0000_reserved  Realization Names: [CUBE[name=test_agg_group]]  Cuboid Ids: [15]  Total scan count: 1000000  Total scan bytes: 51000000  Result row count: 100000  Accept Partial: true  Is Partial Result: false  Hit Exception Cache: false  Storage cache used: false  Is Query Push-Down: false  Is Prepare: false  Trace URL: null  Message: 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_group  group by 1,2

Kylin 日志信息:

==========================[QUERY]===============================  Query Id: 2a9d7422-7268-2805-f1ac-a0fc544602c9  SQL: select "A","B",sum("VALUE")  from test_agg_group  group by 1,2  User: ADMIN  Success: true  Duration: 0.628  Project: 0000_reserved  Realization Names: [CUBE[name=test_agg_group]]  Cuboid Ids: [12]  Total scan count: 100000  Total scan bytes: 4900000  Result row count: 100000  Accept Partial: true  Is Partial Result: false  Hit Exception Cache: false  Storage cache used: false  Is Query Push-Down: false  Is Prepare: false  Trace URL: null  Message: 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_group  where "C" = 100000  group by 1,2

Kylin 日志信息:

==========================[QUERY]===============================  Query Id: e304ae37-f7ec-233b-d353-845e2feba908  SQL: select "A","B",sum("VALUE")  from test_agg_group  where "C" = 100000  group by 1,2  User: ADMIN  Success: true  Duration: 0.806  Project: 0000_reserved  Realization Names: [CUBE[name=test_agg_group]]  Cuboid Ids: [15]  Total scan count: 2  Total scan bytes: 102  Result row count: 2  Accept Partial: true  Is Partial Result: false  Hit Exception Cache: false  Storage cache used: false  Is Query Push-Down: false  Is Prepare: false  Trace URL: null  Message: null  ==========================[QUERY]===============================

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

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

 

总结

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

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

 

了解更多大数据资讯,点击进入Kyligence官网

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!