缘由

近期由于发现线上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 groupleftover = 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 cuboidint 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算法修改相关推荐

  1. 大数据培训技术Kylin核心算法逐层构建算法

    核心算法 Kylin的工作原理就是对数据模型做Cube预计算,并利用计算的结果加速查询: 1)指定数据模型,定义维度和度量: 2)预计算Cube,计算所有Cuboid并保存为物化视图: 预计算过程是K ...

  2. Dijkstra算法修改 + dfs算法的总结

    Dijkstra + dfs 参考Dijkstra算法的改进 前提:Dijkstra算法的思路和实现请看最短路径算法之二:Dijkstra总结 介绍 Dijkstra算法和dfs算法的结合主要解决最短 ...

  3. wand(wead and)算法简介与改造,修改为店铺与品牌的关键字搜索

    前言: 由于新版本的产品已经上线,但是还没有一个搜索的功能,做一个搜索模块出来是当务之急.新的搜索模块将同时实现两个功能,搜索商品,与搜索店铺及品牌.搜索商品关键词部分由陈哥寻找开源的搜索引擎.另一部 ...

  4. Apache Kylin VS Apache Doris

    作者: 康凯森 日期: 2018-04-17 分类: OLAP 1 系统架构 1.1 What is Kylin 1.2 What is Doris 2 数据模型 2.1 Kylin的聚合模型 2.2 ...

  5. Kylin安装与使用

    目录 一.Kylin概述 二.Kylin机构 三.Kylin特点 四.安装 4.1 Kylin依赖环境 4.2 Kylin搭建 4.3 Kylin兼容性问题 4.4 Kylin启动 五.Kylin C ...

  6. Apache Kylin 和 Baidu Palo对比

    1 系统架构 1.1 What is Kylin 1.2 What is Palo 2 数据模型 2.1 Kylin的聚合模型 2.2 Palo的聚合模型 2.3 Kylin Cuboid VS Pa ...

  7. Apache Kylin VS Baidu Palo

    https://blog.bcmeng.com/post/apache-kylin-vs-baidu-palo.html 作者: 康凯森 日期: 2018-04-17 分类: OLAP 1 系统架构 ...

  8. 数据仓库之电商数仓-- 5、即席查询Kylin

    目录 一.Kylin 1.1 Kylin简介 1.1.1 Kylin定义 1.1.2 Kylin相关术语 1.1.3Kylin架构 1.1.4 Kylin特点 1.2 Kylin安装 1.2.1 Ky ...

  9. Kylin集群部署及基本架构简介

    一.基本架构及原理 实现:利用hadoop中MapReduce框架对hive表中的数据进行预计算,将预计算结果缓存至Hbase中,解决TB级数据分析需求 原理架构参考:https://www.cnbl ...

最新文章

  1. Git基础之(二十)——标签管理——创建标签
  2. android 信息添加附件功能,Android实现带附件的邮件发送功能
  3. Maven搭建简单的SS项目
  4. ubuntu终端下快捷键,字体放大缩小等【逐渐完善篇】
  5. Jquery实现无限级树状结构并动态添加增删改等编辑功能
  6. linux下的vconfig配置_Linux系统下安装配置-OpenLDAP-phpLDAPadmin
  7. 22.创建DockWidget
  8. PHP截取UTF-8字符串,解决半字符问题
  9. python笔记(一)获取当前目录路径和文件(抄录)
  10. Java Bitwise Operators
  11. gin上传文件服务器,gin-上传文件
  12. 【GIMP教程探索系列】GIMP将照片修改为符合要求的证件照吗,修改尺寸与像素,压缩图像大小
  13. 易班php,易班PHP-SDK开发
  14. 中山大学非全日制计算机考研,中山大学社会工作非全日制考研经验贴
  15. 有感于《成都私车数量超上海》
  16. 选择消失,只因一千个伤心的理由
  17. vsftp虚拟账户登录失败331 Please specify the password.
  18. ECS云服务器详细配置
  19. 阿波罗java_携程Apollo(阿波罗)安装部署以及java整合实现
  20. 第2章 初学 emWin 的准备工作及其快速上手

热门文章

  1. yota3墨水屏调节对比度_双屏手机YOTA3评测:墨水屏能当“Kindle”用,还支持微信!...
  2. Oracle笔记本(1102)
  3. 求助 opencv视频播放速度变慢
  4. 一个简单的网页计算器-php网站建设代码段分享
  5. Android 中获取指纹(SAH1)签名
  6. 海通证券:云管理平台统一纳管金融云混合基础设施
  7. 制作一个简易的UGUI无限滑动框(Unity)
  8. 魔兽怀旧卓越服务器微信,魔兽怀旧服玩家吐槽:没有了工作室后,永久60级服务器一地鸡毛...
  9. cisp-pte考试复盘及常考题型总结
  10. linux文件删除 能恢复出厂设置密码,OpenWrt固件通过reset键恢复出厂设置步骤