预汇总的多维统计分析(OLAP)靠谱吗?

多维分析涉及的数据量往往很大,基于明细数据进行汇总效率很低,因此会采用预汇总的方式加快查询速度。事先将要查询的结果计算好(预汇总),使用时直接读取预汇总结果就可以获得实时响应,满足交互分析的需要。

预汇总方案分析

预汇总是一种空间换时间的手段,理论上可以把所有维度组合都预汇总,这样就可以满足基于这些维度相对固定的多维分析。但实际应用却有困难,50 个维度的全量预汇总需要的存储空间高达 1MT(按中间 CUBE 大小 1KB 计算,实际显然不可能这么小),需要 100 百万块 1T 的硬盘!!即使从 50 个维度里选出 20 个维度预汇总,也将占用 470000T 的空间,还是需要几十万块硬盘。

关于多维分析预汇总容量的计算可以参考: 多维分析预汇总的存储容量

全量预汇总不靠谱,那么采用部分预汇总如何呢?部分预汇总根据业务事先统计出常用的查询,再基于业务需求建模,工程上也有一些优化的手段。部分预汇总是一种为了同时兼顾存储空间和查询性能的折中办法,虽然有很多不方便的地方,但至少不会面临全量预汇总无法承受的存储成本。

预汇总方案困境

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

1. 非常规聚合

预汇总方案是将测度聚合值先计算好并存储起来,显然,在预汇总阶段没有想到的测度聚合值就无法直接从预汇总的数据中查询出来了。像常见的计数、合计、平均等一般会在预汇总阶段考虑到,但有些如中位数、方差等却很容易被遗漏。类似这些有业务意义的聚合计算有很多,预汇总不可能全部考虑到,未被预存储时就只能临时遍历计算了。

2. 组合聚合

聚合运算还可能组合,它并不是单纯的合计和平均,而是两种聚合运算在不同维度层次上的组合。比如月最大销售额、地区人员收入中位数的均值、…。甚至有可能出现涉及三个或更多聚合运算组合的情况。这些组合聚合不可能在预汇总阶段考虑到,即使把这些值都预存储起来,存储空间还会再多几个数量级,加剧容量问题。仍然需要遍历计算的能力。

3. 条件测度

测度在统计时还可能带有条件。比如,我们想了解一下交易金额大于 100 元以上的订单销售额合计。这里的 100 经常是个参数,在交互查询阶段才临时输入进来,这就不可能事先预汇总了。

条件测度还可能出现多个关联的情况,比如统计销售量超过 10 件的那些订单的销售额。还可能有由测度临时产生的维度。比如按年龄段统计平均收入,而年龄段的划分规则是由参数传递进来的。这些都无法事先预汇总,仍然需要快速硬遍历的功夫。

4. 时间段统计

时间是多维分析中特别重要的一种维度。一般的维度只能以枚举(统计时针对某些特定的维度值)方式切片,而且时间维度却很特殊,它即可以枚举、也可以采用连续区间的方式来做切片。这就可能查询任意两个日期之间的数据,但按维度的预汇总,要么针对每天,要么针对每个月,都是确定的维度(或其层次),而 5 月 8 日到 6 月 12 日这种区间则不是任何维度和层次能描述的。

时间段统计还可能有多个组合关联的情况,比如看看 5 月 8 日到 6 月 12 日间销出的、生产日期在 1 月 9 日到 2 月 17 日之间的货品总额。只考虑一个时间维度的区间,还可以一定程度地利用预汇总数据,基于中间 CUBE 去遍历聚合。但如果涉及多个时间维区间组合查询时,这个问题也会变得非常繁琐,事先预汇总也就无能为力了。

多维分析预汇总方案的功能盲区,不仅仅在于我们常说的明细数据,而还有很多汇总信息也无法解决。采用预汇总用空间换时间,确实能一定程度地提高性能,但只能解决一小部分最简单的需求,而且还面临存储量巨大的问题。把多维分析的效果寄希望于预汇总方案是很不靠谱的。要做好多维分析,硬遍历的功夫是基本的,即使有了预汇总数据,也要在优秀的硬遍历能力辅助下才能发挥更大的作用。

应对方案

硬遍历能力

要做出灵活的多维分析,还是要指望过硬的遍历能力。多维分析运算本身并不算复杂,遍历计算主要是针对维度的过滤。但传统数据库只能用 WHERE 硬算,维度相关的过滤也当作常规运算,不能获得较好的性能。

使用集算器 SPL 可以更好地实现遍历中的高性能维度过滤。集算器 SPL 是一款开源计算引擎,提供了多种维度过滤机制,可以满足多维分析各类场景的需要,试举几例。

布尔维序列是针对枚举维度的优化,在多维分析中除了时间维度几乎都是枚举维度,如产品、地区、类型等。常规的 IN 方法需要进行多次比较判断才能筛选出符合条件的数据(切片),性能很低,IN 的取值越多性能越差。这时可以想办法将查找运算转换成取值运算来提升性能。集算器提供了这种机制,先将枚举维度转换成整数,然后在查询时将切片条件转换成布尔值构成的对位序列,在比较时就可以直接从序列指定位置取出值(true/false 或者空 / 非空)判断结果,快速完成切片操作。
详细内容: 【性能优化】8.4 [多维分析] 布尔维序列
实践案例: 多维分析后台实践 7:布尔维度和二值维度

标签位维度是针对二值维度(取值只有是 / 否或 true/false 两种的枚举维度,比如人员是否结婚、是否上过大学、是否拥有信用卡)做的优化。具体实现时因为标签维度只有两种取值,只要一个位就可以存储了。一个 16 位的小整数可以保存 16 个标签,原本要用 16 个字段来存储的信息现在用一个字段就够了,这种存储方式称为标签位维度。这将大幅度减少存储量也就是硬盘读取量,而且小整数也不影响读取速度。
详细内容: 【性能优化】8.5 [多维分析] 标签位维度
实践案例: 多维分析后台实践 7:布尔维度和二值维度

冗余排序是利用有序来加快读取(遍历)速度的优化手段,具体实现时将所有维度按照 D1 到 Dn 顺序排序后存储一份数据集,再按 Dn 到 D1 数据排序存储一份(冗余一份),使用时总能找到一个数据集使得维度 D 在排序维度列表的前半部分,这样就可以读取相对较大连续的存储区域,从而加快读取速度。
详细内容: 【性能优化】8.3 [多维分析] 冗余排序
实践案例: 多维分析后台实践 4:预汇总和冗余排序

集算器中还提供了很多高效运算机制不仅适用多维分析场景,还可以面向其他数据处理场景,如高性能存储、有序计算、并行计算等等,结合这些能力可以获得更高效的多维分析体验。

辅助预汇总方案

预汇总方案在某些相对固定的查询场景仍然有意义,集算器也提供了一些有特色的预汇总能力。

部分预汇总

部分预汇总会根据业务经验来制定汇总哪些维度组合,也可以根据前端查询动态保存汇总结果。集算器提供了一些部分预汇总的优化机制,包括汇总时使用更低层级的中间 CUBE(Cuboid)而不是基于原始 CUBE;使用都包含某些维度的 Cuboid 时自动选择数据量较小的。

使用集算器构建 CUBE 通过 T.cuboid(Cube1,D1,…;sum(M1),…)函数就可以完成;使用也非常简单:T.cgroups(D1,…;sum(M1),…) cgroups 函数就会按上述的逻辑自动寻找最合适的预汇总数据再计算。

时间段预汇总

针对特殊的时间段统计集算器提供改了时间段预汇总方式。前面我们提过,对于任意时间段的查询不可能事先预汇总出来,但却可以想一些办法。将时间范围的整月数据从月汇总中读取,头尾不到一个月的数据从日汇总中读取,对于时间跨度比较大的查询计算量可以减少数倍到数十倍。

SPL 实现上也很简单,在 cgroup 函数上增加条件参数就可以:

A
1 =file("orders.ctx").open()
2 =A1.cuboid(CubeDay,dt,area;sum(amount))
3 =A1.cuboid(CubeMonth,month@y(dt),area;sum(amount))
4 =A1.cgroup(area;sum(amount);dt>=date(2020,1,22)&&dt<=date(2020,9,8))

诚如前面所说,预汇总只能解决多维分析中一小部分相对简单固定的需求,其他大量常见的需求还需要使用诸如集算器这样的计算引擎实施高效硬遍历才能很好满足,再结合集算器提供的部分预汇总与时间段预汇总功能就可以更好地满足多维分析在性能方面的要求,同时将存储成本降到最低。

覆盖范围广、查询性能高、使用成本低,这才是多维分析理想的使用方式。