1. 程式人生 > >在Apache Kylin中使用Count Distinct

在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