Kylin cuboid算法修改
来源:互联网 发布:ab post json请求 编辑:程序博客网 时间:2024/06/05 15:52
缘由
近期由于发现线上cube的构建时间太慢(一个项目的cube构建前一天的数据一般需要170分钟左右),目前我们接入的应用才三个,如果后期接入更多的cube之后会导致更慢的cube构建速度,于是深入了解了一下cuboid是如何确定的,看了代码之后发现和我们预想的不一样,于是经过咨询社区之后也觉得之前的算法是存在一定的问题(2.x版本已经对此做了修改),因此就准备对cuboid的计算进行修改。
Kylin原有cuboid算法
了解了kylin中如何对cube进行优化(参见OLAP引擎——Kylin介绍和Kylin使用之创建Cube和高级设置)之后,下面来看一下kylin是确定哪些cuboid需要计算,哪些是不需要计算的呢?
在Kylin中保存cube的时候需要对cuboid进行校验,通过三种方案计算出生成cuboid的数量,然后对比三种方法生成的数量是否相同,如果不相同这说明cube的定义存在问题(例如一个hierarchy组的不如维度分布在不同的group中)。在Kylin中,使用位图来计算cuboid,每一个维度在位图上使用一个位置(所以维度超过64就会出现问题),每一个cuboid的值是一个long值,它的二进制中为0的位置表示对应的维度不出现在这个cuboid中,而为1的表示该cuboid是这些维度的组合。保存cube的时候会通过三种方式计算可能产生的cuboid个数,根据个数是否相同来判断cube的定义是否正确:
- 根据树的根节点(base cuboid)计算它的spanning cuboid然后再一次计算每一个spanning cuboid的spanning cuboid,并将它们加入到set中,但是需要保证每一个cuboid的spanning cuboid是不相同的,因为如果重复可能会导致cuboid被重复计算,每一个cuboid在计算spanning cuboid是从它的child cuboid中过滤掉它的兄弟cuboid的child cuboid,这样就能够保证每一个cuboid的spanning是唯一的。
- 从0开始,递增直到base cuboid(位图上所有维度对应的位置都为1),依次递增,对每一个cuboid判断它是否需要被计算,然后统计所有需要计算的cuboid个数。这种方法可能随着维度数的增加变得性能很差,试想32个维度的cube需要2的32次方的遍历。
- 通过数学的方法计算需要计算的cuboid个数。
首先看一下kylin中如何验证一个cuboid是否需要计算(第二种计算方案利用了这种方式判断满足分组的cuboid的计数):
public static boolean isValid(CubeDesc cube, long cuboidID) { RowKeyDesc rowkey = cube.getRowkey(); if (cuboidID < 0) { throw new IllegalArgumentException("Cuboid " + cuboidID + " should be greater than 0"); } if (checkBaseCuboid(rowkey, cuboidID)) { return true; } if (checkMandatoryColumns(rowkey, cuboidID) == false) { return false; } if (checkAggregationGroup(rowkey, cuboidID) == false) { return false; } if (checkHierarchy(rowkey, cuboidID) == false) { return false; } return true;}
可以看出kylin会检查一个cuboid多个属性,按照如下步骤:
- 查看是否大于0,由于使用位图,所以所有的cuboid都必须是大于0的值
- 查看是否base cuboid
- 查看它是否包含所有mandatory维度,并且除去mandatory维度之外还必须包含至少一个其它维度,也就是除去mandatory之后为0的cuboid不需要预计算
- 查看这个cuboid是否符合分组的定义
- 查看这个cuboid是否符合hierarchy维度组的定义。
之所以没有检查derived维度组,是因为derived维度组并不是约束cuboid的,而使用的是替换的方式将一个维度表中的derived维度替换成使用主键的维度。
重点来看一下检查是否满足分组约束的,其他的检查都是比较简单的:
private static boolean checkAggregationGroup(RowKeyDesc rowkey, long cuboidID) { long cuboidWithoutMandatory = cuboidID & ~rowkey.getMandatoryColumnMask(); long leftover; for (AggrGroupMask mask : rowkey.getAggrGroupMasks()) { if ((cuboidWithoutMandatory & mask.uniqueMask) != 0) { leftover = cuboidWithoutMandatory & ~mask.groupMask; return leftover == 0 || leftover == mask.leftoverMask; } } leftover = cuboidWithoutMandatory & rowkey.getTailMask(); return leftover == 0 || leftover == rowkey.getTailMask();}
从这段代码看出,每一个维度组保存了三个mask信息:
- groupMask:每一个组中所有的维度对应的位置都置为1的值。
- uniqueMask:只包含在本组而不包含在后面所有组的那些维度对应位置都置为1的值。
- leftoverMask:不包含在本组中,但是包含在后面其余组中的所有维度对应的位置都置为1的值。
除此之外,leftover表示不包含在所有的mask同时也不是mandatory维度的那些维度。
在检查一个cuboid是否符合组约束的时候首先去除了mandatory维度,然后检查每一个分组,如果该cuboid中有一个维度是某一个group独有的(包含在uniqueMask中),那么说明只需要在该组中检查就可以了,此时判断这个cuboid再去除所有该组的维度之后是否不包含任何维度(说明除去mandatory维度以外不包含任何该组外的维度了)或者它还包含了不在本组但是在后面所有组的所有的维度。最后,如果它不包含在任何组中,那么只需要查看它是否等于leftover就可以了。从这里可以推断出,所有的cuboid包含这两部分:1、每一个组内成员的任意组合(全部成员都包含在一个组里面),2、只有部分维度包含在一个组里面,其余的维度等于这个组的leftoverMask。
优化cuboid算法
但是这种计算cuboid的策略和我们上面分析的不一致,并且这种算法总是考虑每一个组的leftoverMask,所以会导致两个问题:1、分组的顺序影响计算的cuboid,2、分组的时候需要考虑到每一个组的leftover有哪些维度,不容易和查看进行匹配。总体来讲,这是一个较为复杂的逻辑,这会导致我们不能根据可能查询的SQL轻易地推断出如何进行分组,因此我们考虑简化这部分逻辑,目标只有一个:减小cuboid计算量,不再计算第二部分cuboid。
修改之后的isValid函数保持相同的逻辑,而checkAggregationGroup如下:
private static boolean checkAggregationGroup(RowKeyDesc rowkey, long cuboidID) { long cuboidWithoutMandatory = cuboidID & ~rowkey.getMandatoryColumnMask(); long leftover; for (AggrGroupMask mask : rowkey.getAggrGroupMasks()) { //all in one mask group leftover = cuboidWithoutMandatory & ~mask.groupMask; if (leftover == 0) return true; } return false;}
修改之后的组规则只会考虑这个cuboid是否属于某一个分组,这样就边的清晰明了了。
除了这里的修改之外,还有比较重要的修改在于计算每一个cuboid的spanning cuboid,目前采取的策略如下:
- 如果是base cuboide,那么它的spanning cuboid就是所有组的groupMask。
- 查看该cuboid所有小于它的sibling cuboid(1的个数相同,但是位置不同),并将每一个sibing cuboid的child cuboid加入到一个set中。
- 查看该cuboid的child cuboid,如果在2中计算的set中存在则不作为spanning cuboid,否则加入到结果集中。
- 在计算cuboid的child cuboid的时候会遍历所有组,如果属于某个组则从这个cuboid中去掉该组中的某个维度作为它的child cuboid。
通过数学方法计算cuboid数量也需要相应的修改,根据我们优化之后的cuboid计算方法,这个计算可以演化成在多个分组中如果计算出不同的组合个数,例如[1001101, 10001100, 00101101]这三个mask,如果计算包含在其中一个或者多个mask的数的个数,最简单的计算就是分别计算出每一个mask可能的组合数,然后三个masj的组合数相加,在减去他们之间的交集,只不过这里面的交集还有交集,所以需要递归的进行,代码如下:
private static int mathCalculateGroupCount(RowKeyDesc rowkey, long[] groups) { int sum = 0; for(int i = 0 ; i < groups.length ; ++ i) { sum += mathCount(rowkey, groups, i, groups.length - 1); } return sum;}private static int mathCount(RowKeyDesc rowkey, long[] groups, int cur, int end) { long current = groups[cur]; if(current == 0) return 0; //ignore all 0 cuboid int count = mathCalcCuboidCount_combination(rowkey, current) - 1; long[] next = new long[end - cur]; int index = 0; for(int i = cur + 1 ; i <= end ; ++ i) { long com = current & groups[i]; next[index++] = com; } return count - mathCalculateGroupCount(rowkey, next);}
但是cuboid的计算主要是在计算cube的时候使用的,所以需要保证如下几点:
- 新算法计算出的cuboid需要是之前算法计算出的cuboid的子集,如果不能满足,则可能出现查询定位到新算法计算出的cuboid而实际上老数据并没有计算这个cuboid导致返回数据为空,通过分析可以看出新的算法只是在组规则中加强了约束条件。
- 通过新老算法计算出的cuboid在merge的时候会不会出现问题,由于1的保证,在merge的时候会导致merge之后的数据包含在新算法的cuboid包含了新老数据,而其他的cuboid都只包含老数据,所以需要保证查询的时候不会查到老的cuboid,这点是通过cuboid为匹配的情况下往树的上层查找的过程中只会查找已计算的cuboid保证,因此最终还是会查找到新算法计算出的cuboid,就不会导致查询数据为空了。
这种优化带来的好处:
- 缩短每次build的时间,每层需要计算的key变少了,同时下一层的输入也变小了。
- 减小hbase中存储cuboid的空间。
- 对老数据没有任何影响。
缺点:
- 如果没有某一次查询不能命中到某一个分组,需要从base cuboid中扫描,可能导致更大的扫描范围,性能降低。
- 代码修改可能会带来未知的bug。
总结
总体来说,本次对cuboid算法的修改是具有可行性的,但是相对比较冒进的,如果查询的SQL大部分情况下都是确定的,那么这样的修改带来的好处远大于它所带来的查询的影响,目前修改之后运行比较稳定。
- Kylin cuboid算法修改
- Cuboid特征提取算法
- [kylin]Kylin 快速数据立方算法揭秘
- Apache Kylin Cube构建算法
- Kylin
- Kylin
- kylin
- Kylin
- Linux系统(Ubuntu Kylin)修改权限,超级管理员
- 如何计算cuboid的数量
- ubuntu kylin sudoers 修改错误导致系统无法正常使用
- Kylin修改默认hbase namespace命名空间default的解决方案
- Apache Kylin的快速数据立方体算法——概述
- Apache Kylin的快速数据立方体算法——概述
- Apache Kylin的快速数据立方体算法——概述
- 【转】action recognition 特征表达- Cuboid,stip
- Project Euler:Problem 86 Cuboid route
- action recognition 特征表达- Cuboid,stip
- php页面静态化
- linux tail命令的使用方法详解
- Android框架设计模式(五)——Singleton Method
- Windows Dev Intro - Identify Background Process
- uva 10245 The Closest Pair Problem
- Kylin cuboid算法修改
- Octave /Matlab--Control Statements:for,while, if statement----Coursera ML笔记
- HTML5 之 基本标签、属性
- POJ 3368 Frequent values(线段树/RMQ)
- UltraISO制作U盘启动盘安装Win7系统攻略
- 支持向量机通俗导论(理解SVM的三层境界)
- activemq的P2P和订阅发布模型
- 二维树状数组模板
- 存储、读取二进制的图像