在Apache Kylin中使用Count Distinct
雷頓學院大資料:http://www.leidun.site/
在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/
未經同意,不得轉載。
http://lxw1234.com/archives/2016/08/712.htm