多维分析SPL预汇总

62 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详

大家好,我是bug郭,一名双非科班的在校大学生。对C/JAVA、数据结构、Spring系列框架、Linux及MySql、算法等领域感兴趣,喜欢将所学知识写成博客记录下来。 希望该文章对你有所帮助!如果有错误请大佬们指正!共同学习交流

作者简介:

多维分析(OLAP)通常要求极高的响应效率,当涉及的数据量很大时,每次都基于明细数据汇总效率就会很低,人们会考虑采用预汇总的方式加快查询速度,即事先将要查询的结果计算好,使用时直接读取预汇总结果就可以获得实时响应,从而满足交互分析的需要。

不过,将可能的维度组合全部预汇总不太现实,按中间CUBE大小仅1KB计算50个维度的全量预汇总需要的存储空间高达1MT,需要100百万块1T的硬盘,即使只汇总其中20个维度也要占用470000T的空间(多维分析预汇总的存储容量),显然都不能接受。所以,一般会采用部分预汇总的方式,汇总其中一部分维度以平衡存储空间和性能需要。

预汇总方案的困境

其实,即使不考虑容量问题,预汇总也只能满足多维分析中一小部分相对固定的查询需求,稍微复杂灵活的场景就搞不定了,而这些场景在实际业务中大量存在。

  1. 非常规聚合:除了常见的合计、计数外,有些非常规聚合,比如唯一计数、中位数、方差等很可能被遗漏,也无法从其它聚合值计算出来。理论上有无数种聚合运算,不可能被预汇总。

  2. 组合聚合:聚合运算可能组合。比如我们可能关心月平均销售额,这个值是将每天的销售额按月合计后再求平均。它并不是单纯的合计和平均,而是两种聚合运算在不同维度层次上的组合。这些也不太可能事先预汇总。

  3. 条件测度:测度在统计时还可能带有条件。比如,我们想了解一下交易金额大于 100 元以上的订单销售额合计。这个信息也无法在预汇总时处理,因为 100 会是临时输入的参数。

  4. 时间段统计:时间是个特别维度,它即可以枚举、也可以采用连续区间的方式来做切片。查询区间的起止点可能是细粒度(比如到某日),就必须用细粒度的数据再统计,而无法直接使用更高层的预汇总数据。

预汇总的确能一定程度地提高多维分析的性能,但只能应对多维分析中很少的场景,而且还只能部分预汇总,使用场景就更有限了,即使这样还要面临巨大存储空间的问题。把多维分析的效果寄希望于预汇总方案并不靠谱。要做好多维分析,硬遍历的功夫是基本的,即使有了预汇总数据,也要在优秀的硬遍历能力辅助下才能发挥更大的作用。

SPL预汇总

开源的集算器SPL提供了常规多维分析预汇总方式,还有特色的时间段预汇总,更重要的是借助SPL优秀的数据遍历能力还能满足多维分析更广泛的场景需要。

首先看一下SPL的预汇总能力。

部分预汇总

全量预汇总不现实,只能进行部分预汇总,虽然无法达到O(1)的响应速度,但也可以把性能提升几十倍,有一定意义。SPL可以根据需要建立多个预汇总的中间结果。例如,数据表 T 有 A、B、C、D、E 五个维度。根据业务经验就可以预先计算出来了几个最常用的中间结果。

image

上图中cube 占用存储空间的大小用条形长度来表示,cube1 最大,cube2 最小。前端应用来了一个请求,要按照 B、C 做统计汇总。这时 SPL 对多个 cube 自动选择的过程大致如下。

image

第i步,SPL 找到可以利用的 cube 是 cube1 和 cube3。第 ii 步,SPL 发现 cube1 比较大,就会自动选择比较小的 cube3,并在其基础上按 B、C 做分组汇总。

SPL代码示例:

A
1 =file("T.ctx").open()
2 =A1.cuboid(cube1,A,B,C;sum(…),avg(…),…)
3 =A1.cuboid(cube2,A,C,D;sum(…),avg(…),…)
4 =A1.cgroups(B,C;sum(…), avg(…))

使用 cuboid 函数建立预汇总数据(A2和A3),需要起个名字(如cube1),剩下的参数是维度和汇总测度;A4使用时通过cgroups函数就会自动利用上面的规则使用中间cube并选择数据量最小的使用了。

时间段预汇总

时间是多维分析中特别重要的一种维度,它即可以枚举、也可以采用连续区间的方式来做切片。比如业务中经常要查询如 5 月 8 日到 6 月 12 日之间的销售额合计,这个起止时间点也是查询时作为参数传递进来的,具有很强的随意性。时间段统计还可能有多个组合关联的情况,比如看看 5 月 8 日到 6 月 12 日间销出的、生产日期在 1 月 9 日到 2 月 17 日之间的货品总额。类似这种时间段统计有很强的业务意义,但却无法使用常规预汇总方案应对。

针对这种特殊的时间段统计,SPL提供了时间段预汇总方式。例如,订单表已经有一个按照订单日期预汇总的cube1,那么我们可以在此基础上再增加一个按月预汇总的cube2。这时要计算 2018 年 1 月 22 日到 9 月 8 日的金额汇总值,大致过程会是这样:

image

将时间段分成三段,2月到8月整月的数据基于月汇总cube2计算聚合值,再使用cube1计算 1 月 22 日到 1 月 31 日和 9 月 1 日到 9 月 8 日的聚合值,涉及的计算量是 7(2 月 -8 月)+10(1 月 22 日 -1 月 31 日)+8(9 月 1 日 -9 月 8 日)=25,而如果使用cube1数据聚合,其计算量是 223(从 1 月 22 日到 9 月 8 日的天数),几乎减少了 10 倍。

SPL代码示例:

A
1 =file("orders.ctx").open()
2 =A1.cuboid(cube1,odate,dept;sum(amt))
3 =A1.cuboid(cube2,month@y(odate),dept;sum(amt))
4 =A1.cgroups(dept;sum(amt);odate>=date(2018,1,22)&&dt<=date(2018,9,8))

cgroups 函数增加了条件参数,SPL 发现有时间段条件和更高层次的预汇总数据,则会使用时间段预汇总机制来减少运算量。本例中,就会分别从 cube1 和 cube2 中读取相应数据再来汇总。