Kylin Cube的優化
在沒有采取任何優化措施的情況下,Kylin會對每一種維度的組合進行預計算,每種維度的組合的預計算結果被稱為Cuboid。假設有4個維度,我們最終會有24 =16個Cuboid需要計算。
但在現實情況中,用戶的維度數量一般遠遠大於4個。假設用戶有10 個維度,那么沒有經過任何優化的Cube就會存在210 =1024個Cuboid;而如果用戶有20個維度,那么Cube中總共會存在220 =1048576個
Cuboid。雖然每個Cuboid的大小存在很大的差異,但是單單想到Cuboid的數量就足以讓人想象到這樣的Cube對構建引擎、存儲引擎來說壓力有多么巨大。因此,在構建維度數量較多的Cube時,尤其要注意
Cube的剪枝優化(即減少Cuboid的生成)。優化核心即剪掉沒必要的cuboid,把它的數量減少點就會更快
1. 維度表與事實表
1、維度表
- 要具有數據一致性,主鍵值必須是唯一的(否則 Kylin 構建過程會報錯);
- 維度表越小越好,因為 Kylin 會將維度表加載到內存中供查詢,過大的表不適合作為維度表,默認的閾值是 300MB;
- 改變頻率低,Kylin 會在每次構建中試圖重用維度表的快照(Snapshot),如果維度表經常改變的話,重用就會失效,這就會導致要經常對維度表創建快照;
- 維度表最好不要是 Hive 視圖(View),因為每次都需要將視圖進行物化,從而導致額外的時間開銷。
2、事實表
- 移除不參與 Cube 構建的字段,可以提升構建速度,降低 Cube 構建結果的大小;
- 盡可能將事實表進行維度拆分,提取公用的維度;
- 保證維度表與事實表的映射關系,過濾無法映射的記錄。
如果維度與事實表無法映射,某些字段(數據類型為 number 系列)會遇到構建失敗的問題(
numberFormatException('\N')
,\N
是為 Hive 中 NULL 的實際存儲內容);此外,在進行 Left Join 的時候會產生大量的 NULL,這些 NULL 值在真正查詢中根本沒有任何作用。
3、分區表
Hive 表支持多分區(Partition),簡單地說,一個分區就是一個文件目錄,存儲了特定的數據文件。當有新的數據生成的時候,可以將數據加載到指定的分區,讀取數據的時候也可以指定分區。對於 SQL 查
詢,如果查詢中指定了分區列的屬性條件,則 Hive 會智能地選擇特定分區(也就是目錄),從而避免全量數據的掃描,減少讀寫操作對集群的壓力。
Kylin 支持增量的 Cube 構建,通常是按時間屬性來增量地從 Hive 表中抽取數據。如果 Hive 表正好是按此時間屬性做分區的話,那么就可以利用到 Hive 分區的好處,每次在 Hive 構建的時候都可以直接跳過不
相干日期的數據,節省 Cube 構建的時間。這樣的列在 Kylin 里也稱為分割時間列(Partition Time Column),通常它應該也是 Hive 表的分區列。
2. Cube 構建優化
1、維度優化
- 分析查詢條件,不參與 Group By 或者 Where 過濾的條件維度一定不要勾選;
- 一般而言,Left Join 右側表里面的字段均可以作為 Derived (衍生)維度;
- 必需維度、層級維度、聯合維度、衍生維度四種維度優化方案;
- 聚合組進一步對維度組合進行優化。
必需維度、層級維度、聯合維度、衍生維度以及聚合組。
使用聚合組
聚合組(Aggregation Group)是一種強大的剪枝工具。聚合組假設一個Cube的所有維度均可根據業務需求划分成若干組(當然也可以是一個組),由於同一個組內的維度可能同時被同一個查詢用到,因
此會表現出更加緊密的內在關聯。每個分組的維度集合均是Cube所有維度的一個子集,不同的分組各自擁有一套維度集合,它們可能與其他分組有相同的維度,也可能沒有相同的維度。每個分組各自獨立地根
據自身的規則貢獻出一批需要被物化的Cuboid,所有分組貢獻的Cuboid的並集就成為了當前Cube中所有需要物化的Cuboid的集合。不同的分組有可能會貢獻出相同的Cuboid,構建引擎會察覺到這點,並且保證
每一個Cuboid無論在多少個分組中出現,它都只會被物化一次。
聚合組的設計非常靈活,甚至可以用來描述一些極端的設計。假設我們的業務需求非常單一,只需要某些特定的Cuboid,那么可以創建多個聚合組,每個聚合組代表一個Cuboid。具體的方法是在聚合組中先包
含某個Cuboid所需的所有維度,然后把這些維度都設置為強制維度。這樣當前的聚合組就只能產生我們想要的那一個Cuboid了。
再比如,有的時候我們的Cube中有一些基數非常大的維度,如果不做特殊處理,它就會和其他的維度進行各種組合,從而產生一大堆包含它的Cuboid。包含高基數維度的Cuboid在行數和體積上往往非常龐大,
這會導致整個Cube的膨脹率變大。如果根據業務需求知道這個高基數的維度只會與若干個維度(而不是所有維度)同時被查詢到,那么就可以通過聚合組對這個高基數維度做一定的“隔離”。我們把這個高基數的
維度放入一個單獨的聚合組,再把所有可能會與這個高基數維度一起被查詢到的其他維度也放進來。這樣,這個高基數的維度就被“隔離”在一個聚合組中了,所有不會與它一起被查詢到的維度都沒有和它一起出
現在任何一個分組中,因此也就不會有多余的Cuboid產生。這點也大大減少了包含該高基數維度的Cuboid的數量,可以有效地控制Cube的膨脹率。
對於每個分組內部的維度,用戶可以使用如下三種可選的方式定義,它們之間的關系,具體如下。
1)強制維度(Mandatory),如果一個維度被定義為強制維度,那么這個分組產生的所有Cuboid中每一個Cuboid都會包含該維度。每個分組中都可以有0個、1個或多個強制維度。如果根據這個分組的業務邏
輯,則相關的查詢一定會在過濾條件或分組條件中,因此可以在該分組中把該維度設置為強制維度。
在后續的查詢中是否每次group by都會包含某維度
2)層級維度(Hierarchy),每個層級包含兩個或更多個維度。假設一個層級中包含D1,D2…Dn這n個維度,那么在該分組產生的任何Cuboid中, 這n個維度只會以(),(D1),(D1,D2)…(D1,D2…
Dn)這n+1種形式中的一種出現。每個分組中可以有0個、1個或多個層級,不同的層級之間不應當有共享的維度。如果根據這個分組的業務邏輯,則多個維度直接存在層級關系,因此可以在該分組中把這些維度
設置為層級維度。層級之間是包含關系。
用戶選擇的維度中常常會出現具有層級關系的維度。例如對於國家(country)、省份(province)和城市(city)這三個維度,從上而下來說國家/省份/城市之間分別是一對多的關系。
也就是說,用戶對於這三個維度的查詢可以歸類為以下三類: 1. group by country 2. group by country, province(等同於group by province) 3. group by country, province, city(等同於 group by city) 如果ABCD 四個維度中ABC這三個被設置為層級維度, abc=ac=bc 則 abcd=acd=bcd,所以剪掉acd,bcd,保留abcd足以, 則最終生成的cube:
3)聯合維度(Joint),每個聯合中包含兩個或更多個維度,如果某些列形成一個聯合,那么在該分組產生的任何Cuboid中,這些聯合維度要么一起出現,要么都不出現。每個分組中可以有0個或多個聯合,但
是不同的聯合之間不應當有共享的維度(否則它們可以合並成一個聯合)。如果根據這個分組的業務邏輯,多個維度在查詢中總是同時出現,則可以在該分組中把這些維度設置為聯合維度。
A B C為聯合維度,3個維度在一塊才有意義;
用戶有時並不關心維度之間各種細節的組合方式,例如用戶的查詢語句中僅僅會出現 group by A, B, C,而不會出現 group by A, B 或者 group by C 等等這些細化的維度組合。這一類問題就是聯合維度所解決的問題。例如將維度 A、B 和 C 定義為聯合維度,Apache Kylin 就僅僅會構建 Cuboid ABC,而 Cuboid AB、BC、A 等等Cuboid 都不會被生成。
A B C為聯合維度,則要么都在,要么都不在,最終的Cuboid 數目從 16 減少到 4。
4)衍生(推導)維度
衍生(推導)維度必須來自從表(維表),這類維度的意思是可推導的維度,需要該維度對應的一個或者多個列可以和維度表的主鍵是一對一的,是一種強映射,這種維度可以大大減少cuboid個數
normal維度來自主表,derived從表的維度默認會被外鍵(與主鍵一一對應)推導出來;如果在某種情況下不合理可以改edit改為normal,推導維度沒必要選聚合組,默認也不會讓我們選,;
Fact表:A(a,b,c)
Lookup表:B(x,y,z)
如果維度c 中,每種情況都唯一對應一種 x,y。即 abc==abxy,或者說所有xy的組合都可以替換為c。所以可以將 x,y 設置為derived維度,可以減少cuboid的個數。
在查詢時:select xx from xx group by x,y;會被kylin等價轉換為select ... group by c;
並發粒度優化
當Segment中某一個Cuboid的大小超出一定的閾值時,系統會將該Cuboid的數據分片到多個分區中,以實現Cuboid數據讀取的並行化,從而優化Cube的查詢速度。具體的實現方式如下:構建引擎根據Segment
估計的大小,以及參數“kylin.hbase.region.cut”的設置決定Segment在存儲引擎中總共需要幾個分區來存儲,如果存儲引擎是HBase,那么分區的數量就對應於HBase中的Region數量。kylin.hbase.region.cut的默
認值是5.0,單位是GB,也就是說對於一個大小估計是50GB的Segment,構建引擎會給它分配10個分區。用戶還可以通過設置kylin.hbase.region.count.min(默認為1)和kylin.hbase.region.count.max(默認為
500)兩個配置來決定每個Segment最少或最多被划分成多少個分區。
由於每個Cube的並發粒度控制不盡相同,因此建議在Cube Designer 的Configuration Overwrites(上圖所示)中為每個Cube量身定制控制並發粒度的參數。假設將把當前Cube的kylin.hbase.region.count.min設
置為2,kylin.hbase.region.count.max設置為100。這樣無論Segment的大小如何變化,它的分區數量最小都不會低於2,最大都不會超過100。相應地,這個Segment背后的存儲引擎(HBase)為了存儲這個
Segment,也不會使用小於兩個或超過100個的分區。我們還調整了默認的kylin.hbase.region.cut,這樣50GB的Segment基本上會被分配到50個分區,相比默認設置,我們的Cuboid可能最多會獲得5倍的並發量。
3. 其他優化
- RowKeys 順序:Mandatory 維度、where 過濾條件中出現頻率較多的維度、高基數維度、低基數維度;
- ShardBy 設置:建議選擇基數較大的列作為 ShardBy 列,以使得數據可以均勻分布;
- 數據壓縮:Kylin 針對維度字典以及維度表快照采用了特殊的壓縮算法,對於 HBase 中的聚合計算數據利用了 Hadoop 的 LZO 或者是 Snappy 等壓縮算法,從而保證存儲在 HBase 以及內存中的數據盡可能地小;
- 對於大的事實表可以采用分區來增量構建,然后設置定期自動合並(Merge)操作;
- 事實表中日期數據類型為盡可能設置為 date;
- ShardBy 字段有助於全部數據分散分布在各個 Region 中,有助於防止出現數據傾斜等問題;
- ShardBy 字段為 True 后,同一個值的數據會存儲在一起,便於批量撈取數據。
4. kylin構建工具對cube的分析
----(對構建完成后的cube進行分析)
Apache Kylin提供了一個簡單的工具,供用戶檢查Cube中哪些Cuboid 最終被預計算了,稱其為被物化(Materialized)的Cuboid。同時,這種方法還能給出每個Cuboid所占空間的估計值。由於該工具需要
在對數據進行一定階段的處理之后才能估算Cuboid的大小,因此一般來說只能在Cube構建完畢之后再使用該工具。目前關於這一點也是該工具的一大不足,由於同一個Cube的不同Segment之間僅是輸入數據不
同,模型信息和優化策略都是共享的,所以不同Segment中哪些Cuboid被物化哪些沒有被物化都是一樣的。因此只要Cube中至少有一個Segment,就能使用如下的命令行工具去檢查這個Cube中的Cuboid狀態:
[kris@hadoop101 kylin]$ bin/kylin.sh org.apache.kylin.engine.mr.common.CubeStatsReader cube_partition

Sampling percentage: 100 Mapper overlap ratio: 1.0 Mapper number: 1 Length of dimension DEFAULT.EMP_PARTITION.JOB is 1 Length of dimension DEFAULT.EMP_PARTITION.MGR is 1 Length of dimension DEFAULT.EMP_PARTITION.DEPTNO is 1 Length of dimension DEFAULT.EMP_PARTITION.HIRE_DATE is 1 |---- Cuboid 1111, est row: 14, est MB: 0 |---- Cuboid 0111, est row: 14, est MB: 0, shrink: 100% |---- Cuboid 0011, est row: 14, est MB: 0, shrink: 100% ##收縮比例為100%,相當於父維度0111是沒有收縮的,應該去掉這個節點,他和0011是一模一樣 |---- Cuboid 0001, est row: 13, est MB: 0, shrink: 92.86% ##兩個列分組更精細即父維度更精細,子維度相對於父維度應該差別更大,如果差別小就應該砍掉,砍的維度越多,Cuboid越少。 |---- Cuboid 0010, est row: 3, est MB: 0, shrink: 21.43% |---- Cuboid 0101, est row: 14, est MB: 0, shrink: 100% |---- Cuboid 0100, est row: 7, est MB: 0, shrink: 50% |---- Cuboid 0110, est row: 9, est MB: 0, shrink: 64.29% |---- Cuboid 1011, est row: 14, est MB: 0, shrink: 100% |---- Cuboid 1001, est row: 14, est MB: 0, shrink: 100% |---- Cuboid 1000, est row: 5, est MB: 0, shrink: 35.71% |---- Cuboid 1010, est row: 9, est MB: 0, shrink: 64.29% |---- Cuboid 1101, est row: 14, est MB: 0, shrink: 100% |---- Cuboid 1100, est row: 8, est MB: 0, shrink: 57.14% |---- Cuboid 1110, est row: 10, est MB: 0, shrink: 71.43%
從以上分析結果的下半部分可看到,所有的Cuboid及它的分析結果都以樹狀的形式打印了出來。
在這棵樹中,每個節點代表一個Cuboid,每個Cuboid都由一連串1或0的數字組成,如果數字為0,則代表這個Cuboid中不存在相應的維度;如果數字為1,則代表這個Cuboid中存在相應的維度。有幾個1代表有幾個維度。
除了最頂端的Cuboid之外,每個Cuboid都有一個父親Cuboid,且都比父親Cuboid少了一個“1”。其意義是這個Cuboid就是由它的父親節點減少一個維度聚合而來的(上卷)。
最頂端的Cuboid稱為Base Cuboid,它直接由源數據計算而來。 每行Cuboid的輸出中除了0和1的數字串以外,后面還有每個Cuboid 的的行數與父親節點的對比(Shrink值),比如 row: 13 / row: 14 = 92.86%
所有Cuboid行數的估計值之和應該等於Segment的行數估計值,每個Cuboid都是在它的父親節點的基礎上進一步聚合而成的,因此從理論上說每個Cuboid無論是行數還是大小都應該小於它的父親。
在這棵樹中,我們可以觀察每個節點的Shrink值,如果該值接近100%,則說明這個Cuboid雖然比它的父親Cuboid少了一個維度,但是並沒有比它的父親Cuboid少很多行數據。
換而言之,即使沒有這個Cuboid, 我們在查詢時使用它的父親Cuboid,也不會有太大的代價。那么我們就可以對這個Cuboid進行剪枝操作。
5. 膨脹率(Expansion Rate)來分析
還有一種更為簡單的方法可以幫助我們判斷Cube是否已經足夠優化。在Web GUI的Model頁面選擇一個READY狀態的Cube,當我們把光標移到該Cube的Cube Size列時,Web GUI會提示Cube的源數據大小,
以及當前Cube的大小除以源數據大小的比例,稱為膨脹率(Expansion Rate),如圖
一般來說,Cube的膨脹率應該在0%~1000%之間,如果一個Cube的膨脹率超過1000%,那么Cube管理員應當開始挖掘其中的原因。通常,膨脹率高有以下幾個方面的原因。
1)Cube中的維度數量較多,且沒有進行很好的Cuboid剪枝優化,導致Cuboid數量極多;
2)Cube中存在較高基數的維度,導致包含這類維度的每一個Cuboid占用的空間都很大,這些Cuboid累積造成整體Cube體積變大;
3)存在比較占用空間的度量,例如Count Distinct,因此需要在Cuboid的每一行中都為其保存一個較大的寄存器,最壞的情況將會導致Cuboid中每一行都有數十KB,從而造成整個Cube的體積變大;
6. 注意事項
1、哪些維度參與了 Cuboid 構建?
很多人會誤以為只有聚合組 Includes 里面被選擇的維度才參與 Cube 構建的計算,其實真正參與計算是 Dimensions 設置界面選擇為 Default 的那些維度,還有 Left Join 的字段(如果其 Derived 字段被勾選)。
簡單來說,在 RowKeys 排序界面看到的維度都會參與 Cuboid 的構建,如果在 RowKeys 看到不想參與計算的維度,可以到 Dimensions 設置界面進行修改。
2、結合業務設置層級維度
層級維度在設置的時候一定要深刻理解業務,例如:企業到底歸屬到行業類型下還是歸屬到區域類型下?從層級結構的角度來講都是可以的,深層次挖掘業務需求后我們會發現,凡是查詢企業的時候均附帶有區域的過濾條件,因此將企業歸屬到區域的層級下比歸屬到行業類型下更加有效。
3、善於嘗試
在實踐中,可能會遇到各種不確定的思路,最佳的做法是去實踐,多建立 Model 和 Cube,從不同層次去優化,去嘗試,在實踐中不斷總結經驗。
Kylin的角色為兩台all、一台query
在kylin-3.1.0/conf/kylin.properties配置文件中如下的不同:
kylin.server.mode=all #角色為all。
kylin.server.mode=query #角色為query。