Kylin系列-在Apache Kylin中使用Count Distinct

在OLAP多维分析中,Count Distinct(去重计数)是一种非常常用的指标度量,比如一段时间内的UV、活跃用户数等等;
从1.5.3开始,Apache Kylin提供了两种Count Distinct计算方式,一种是近似的,一种是精确的,精确的Count Distinct指标在Build时候
会消耗更多的资源(内存和存储),Build的过程也比较慢;

近似Count Distinct

Apache Kylin使用HyperLogLog算法实现了近似Count Distinct,提供了错误率从9.75%到1.22%几种精度供选择;
算法计算后的Count Distinct指标,理论上,结果最大只有64KB,最低的错误率是1.22%;
这种实现方式用在需要快速计算、节省存储空间,并且能接受错误率的Count Distinct指标计算。

 

精准Count Distinct

从1.5.3版本开始,Kylin中实现了基于bitmap的精确Count Distinct计算方式。当数据类型为tiny int(byte)、small int(short)以及int,
会直接将数据值映射到bitmap中;当数据类型为long,string或者其他,则需要将数据值以字符串形式编码成dict(字典),再将字典ID映射到bitmap;

 

指标计算后的结果,并不是计数后的值,而是包含了序列化值的bitmap.这样,才能确保在任意维度上的Count Distinct结果是正确的。
这种实现方式提供了精确的无错误的Count Distinct结果,但是需要更多的存储资源,如果数据中的不重复值超过百万,结果所占的存储应该会达到几百MB。

全局字典(Global Dictionary)

默认情况下,Kylin在每个Segment中,将数据值编码到一个字典中,同一个数据值,在不同Segment中编码后的ID值也是不同的,
因此在这跨两个Segment中进行Count Distinct计算时候,结果是不正确的;

 

在1.5.3版本中,Kylin引进了”Global Dictionary”,用来确保同一个数据值编码后的ID值始终是相同的,与此同时,字典的容量也进行了扩充,
一个字典的最大容量达到了20亿,之前默认的字典最大容量为500万。全局字典可以代替之前的默认字典。

当前版本1.5.3的UI中,没有提供定义全局字典的地方,需要手动修改Cube的json:

 
 
  1. "dictionaries": [
  2. {
  3. "column": "SUCPAY_USERID",
  4. "reuse": "USER_ID",
  5. "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder"
  6. }
  7. ]

“column”是需要编码(进行Count Distinct计算)的字段,”builder”指定了字典的builder类,目前只能是”org.apache.kylin.dict.GlobalDictionaryBuilder”.
“reuse”是用来优化字典的,当多个字段的值是同一个数据集的时候,指定复用同一个字典即可,不需要再建立字典,后面会详细说明。

全局字典不能用在维度的编码中,如果一个字段即是维度,又是Count Distinct指标,那么就需要为维度指定其他的编码方式。

Example

 
 
  1. | DT | USER_ID | FLAG1 | FLAG2 | USER_ID_FLAG1 | USER_ID_FLAG2 |
  2. | :———-: | :——: | :—: | :—: | :————-: | :————-: |
  3. | 2016-06-08 | AAA | 1 | 1 | AAA | AAA |
  4. | 2016-06-08 | BBB | 1 | 1 | BBB | BBB |
  5. | 2016-06-08 | CCC | 0 | 1 | NULL | CCC |
  6. | 2016-06-09 | AAA | 0 | 1 | NULL | AAA |
  7. | 2016-06-09 | CCC | 1 | 0 | CCC | NULL |
  8. | 2016-06-10 | BBB | 0 | 1 | NULL | BBB |

表中有几个基础列:DT、USER_ID、FLAG1、FLAG2;
还有两个条件列:USER_ID_FLAG1=if(FLAG1=1,USER_ID,null)、USER_ID_FLAG2=if(FLAG2=1,USER_ID,null);假设Cube按天Build,有3个Segments。

如果不使用全局字典,在一个Segment(一天数据)中计算Count Distinct是准确的,但是如果在两个Segment中计算Count Distinct,结果是错误的

 
 
  1. select count(distinct user_id_flag1) from lxw1234 where dt in ('2016-06-08', '2016-06-09')

这个查询结果是将会是2,而不是3, 因为在Segment 2016-06-08的字典中,AAA的编码ID为1,BBB的编码ID为2,而在Segment 2016-06-09中,CCC的编码ID也为1,因此,编码ID去重后,结果是2.

如果使用下面的方式配置了全局字典,那么这三个值的编码ID为: AAA=>1, BBB=>2, CCC=>3,去重后的结果是正确的3。

“dictionaries”: [ { “column”: “USER_ID_FLAG1″, “builder”: “org.apache.kylin.dict.GlobalDictionaryBuilder” } ]

事实上,USER_ID_FLAG1和USER_ID_FLAG2都是USER_ID的子集,这时候,只需要使用USER_ID来创建全局字典,那么另外两个,其实可以复用的,这就是前面提到的reuse。

 
 
  1. "dictionaries": [
  2. { "column": "USER_ID", "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder" },
  3. { "column": "USER_ID_FLAG1", "reuse": "USER_ID", "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder" },
  4. { "column": "USER_ID_FLAG2", "reuse": "USER_ID", "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder" }
  5. ]

性能优化

全局字典是比较大的,在Build时候,”Build Base Cuboid Data”这一步会消耗较长时间。
如果字典大小超过Mapper的内存大小时候,字典需要消耗大量时间在缓存加载和回收上,解决该问题的办法是修改Cube的参数,适当增大Mapper使用的内存:
kylin.job.mr.config.override.mapred.map.child.java.opts=-Xmx8g
kylin.job.mr.config.override.mapreduce.map.memory.mb=8500

总结

选择哪种Count Distinct计算方式呢?
1. 如果能接受1.22%以内的误差,近似计算肯定是最好的方式;
2. 如果业务需要精确去重计数,那么肯定得选择精确Count Distinct;
3. 如果不需要跨Segment(天)的去重,或者字段值是tinyint/smallint/int, 或者字段去重后的值小于500万,那么就是用默认字典;
否则,就需要配置全局字典,同时,如果可以,则是用”reuse”来进行优化。

下一篇文章中,将介绍使用精确Count Distinct对1.5亿string类型数据的精确去重示例。

 

本文翻译自:http://kylin.apache.org/blog/2016/08/01/count-distinct-in-kylin/

未经同意,不得转载。


转自:lxw的大数据田地 » 在Apache Kylin中使用Count Distinct

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值