微信搜索lxw1234bigdata | 邀请体验:数阅–数据管理、OLAP分析与可视化平台 | 赞助作者:赞助作者

在Apache Kylin中使用Count Distinct

Kylin lxw1234@qq.com 17951℃ 1评论

在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:

"dictionaries": [
    {
       "column": "SUCPAY_USERID",
	 	   "reuse": "USER_ID",
       "builder": "org.apache.kylin.dict.GlobalDictionaryBuilder"
    }
]

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

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

Example

| DT         | USER_ID | FLAG1 | FLAG2 | USER_ID_FLAG1 | USER_ID_FLAG2 |
| :———-:     | :——:    | :—:   | :—:   | :————-:       | :————-: |
| 2016-06-08 | AAA     | 1     | 1     | AAA           | AAA |
| 2016-06-08 | BBB     | 1     | 1     | BBB           | BBB |
| 2016-06-08 | CCC     | 0     | 1     | NULL          | CCC |
| 2016-06-09 | AAA     | 0     | 1     | NULL          | AAA |
| 2016-06-09 | CCC     | 1     | 0     | CCC           | NULL |
| 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,结果是错误的

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。

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

性能优化

全局字典是比较大的,在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

喜欢 (13)
分享 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(1)个小伙伴在吐槽
  1. 字典容量20亿的数字怎么得到的
    冰点2017-03-10 17:24 回复