Hbase基礎(十三):Kylin Cube構建優化
從之前章節的介紹可以知道,在沒有采取任何優化措施的情況下,Kylin會對每一種維度的組合進行預計算,每種維度的組合的預計算結果被稱為Cuboid。假設有4個維度,我們最終會有24=16個Cuboid需要計算。
但在現實情況中,用戶的維度數量一般遠遠大於4個。假設用戶有10 個維度,那麼沒有經過任何優化的Cube就會存在210=1024個Cuboid;而如果用戶有20個維度,那麼Cube中總共會存在220=1048576個Cuboid。雖然每個Cuboid的大小存在很大的差異,但是單單想到Cuboid的數量就足以讓人想象到這樣的Cube對構建引擎、存儲引擎來說壓力有多麼巨大。因此,在構建維度數量較多的
1 找到問題Cube
1.1 檢查Cuboid數量
Apache Kylin提供了一個簡單的工具,供用戶檢查Cube中哪些Cuboid 最終被預計算了,我們稱其為被物化(Materialized)的Cuboid。同時,這種方法還能給出每個Cuboid所佔空間的估計值。由於該工具需要在對資料進行一定階段的處理之後才能估算Cuboid的大小,因此一般來說只能在Cube構建完畢之後再使用該工具。目前關於這一點也是該工具的一大不足,由於同一個Cube的不同Segment之間僅是輸入資料不同,模型資訊和優化策略都是共享的,所以不同
bin/kylin.sh org.apache.kylin.engine.mr.common.CubeStatsReader CUBE_NAME
CUBE_NAME:想要檢視的cube的名字
例如:
[atguigu@hadoop102 kylin]$ bin/kylin.sh org.apache.kylin.engine.mr.common.CubeStatsReader FirstCube … … … … Statistics of FirstCube[FULL_BUILD] Cube statistics hll precision:14 Total cuboids: 7 Total estimated rows: 51 Total estimated size(MB): 3.027915954589844E-4 Sampling percentage: 100 Mapper overlap ratio: 1.0 Mapper number: 1 Length of dimension DEFAULT.EMP.JOB is 1 Length of dimension DEFAULT.EMP.MGR is 1 Length of dimension DEFAULT.EMP.DEPTNO is 1 |---- Cuboid 111, est row: 10, est MB: 0 |---- Cuboid 011, est row: 9, est MB: 0, shrink: 90% |---- Cuboid 001, est row: 3, est MB: 0, shrink: 33.33% |---- Cuboid 010, est row: 7, est MB: 0, shrink: 77.78% |---- Cuboid 101, est row: 9, est MB: 0, shrink: 90% |---- Cuboid 100, est row: 5, est MB: 0, shrink: 55.56% |---- Cuboid 110, est row: 8, est MB: 0, shrink: 80%
從分析結果的下半部分可以看到,所有的Cuboid及它的分析結果都以樹狀的形式列印了出來。在這棵樹中,每個節點代表一個Cuboid,每個Cuboid都由一連串1或0的數字組成,如果數字為0,則代表這個Cuboid中不存在相應的維度;如果數字為1,則代表這個Cuboid中存在相應的維度。除了最頂端的Cuboid之外,每個Cuboid都有一個父親Cuboid,且都比父親Cuboid少了一個“1”。其意義是這個Cuboid就是由它的父親節點減少一個維度聚合而來的(上卷)。最頂端的Cuboid稱為Base Cuboid,它直接由源資料計算而來。
每行Cuboid的輸出中除了0和1的數字串以外,後面還有每個Cuboid 的的行數與父親節點的對比(Shrink值)。所有Cuboid行數的估計值之和應該等於Segment的行數估計值,每個Cuboid都是在它的父親節點的基礎上進一步聚合而成的,因此從理論上說每個Cuboid無論是行數還是大小都應該小於它的父親。在這棵樹中,我們可以觀察每個節點的Shrink值,如果該值接近100%,則說明這個Cuboid雖然比它的父親Cuboid少了一個維度,但是並沒有比它的父親Cuboid少很多行資料。換而言之,即使沒有這個Cuboid, 我們在查詢時使用它的父親Cuboid,也不會有太大的代價。那麼我們就可以對這個Cuboid進行剪枝操作。
1.2 檢查Cube大小
還有一種更為簡單的方法可以幫助我們判斷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體積變大;
因此,對於Cube膨脹率居高不下的情況,管理員需要結合實際資料進行分析,可靈活地運用接下來介紹的優化方法對Cube進行優化。
2 優化構建
2.1 使用聚合組
聚合組(Aggregation Group)是一種強大的剪枝工具。聚合組假設一個Cube的所有維度均可以根據業務需求劃分成若干組(當然也可以是一個組),由於同一個組內的維度更可能同時被同一個查詢用到,因此會表現出更加緊密的內在關聯。每個分組的維度集合均是Cube所有維度的一個子集,不同的分組各自擁有一套維度集合,它們可能與其他分組有相同的維度,也可能沒有相同的維度。每個分組各自獨立地根據自身的規則貢獻出一批需要被物化的Cuboid,所有分組貢獻的Cuboid的並集就成為了當前Cube中所有需要物化的Cuboid的集合。不同的分組有可能會貢獻出相同的Cuboid,構建引擎會察覺到這點,並且保證每一個Cuboid無論在多少個分組中出現,它都只會被物化一次。
對於每個分組內部的維度,使用者可以使用如下三種可選的方式定義,它們之間的關係,具體如下。
1)強制維度(Mandatory),如果一個維度被定義為強制維度,那麼這個分組產生的所有Cuboid中每一個Cuboid都會包含該維度。每個分組中都可以有0個、1個或多個強制維度。如果根據這個分組的業務邏輯,則相關的查詢一定會在過濾條件或分組條件中,因此可以在該分組中把該維度設定為強制維度。
2)層級維度(Hierarchy),每個層級包含兩個或更多個維度。假設一個層級中包含D1,D2…Dn這n個維度,那麼在該分組產生的任何Cuboid中, 這n個維度只會以(),(D1),(D1,D2)…(D1,D2…Dn)這n+1種形式中的一種出現。每個分組中可以有0個、1個或多個層級,不同的層級之間不應當有共享的維度。如果根據這個分組的業務邏輯,則多個維度直接存在層級關係,因此可以在該分組中把這些維度設定為層級維度。
3)聯合維度(Joint),每個聯合中包含兩個或更多個維度,如果某些列形成一個聯合,那麼在該分組產生的任何Cuboid中,這些聯合維度要麼一起出現,要麼都不出現。每個分組中可以有0個或多個聯合,但是不同的聯合之間不應當有共享的維度(否則它們可以合併成一個聯合)。如果根據這個分組的業務邏輯,多個維度在查詢中總是同時出現,則可以在該分組中把這些維度設定為聯合維度。
這些操作可以在Cube Designer的Advanced Setting中的AggregationGroups區域完成,如下圖所示。
聚合組的設計非常靈活,甚至可以用來描述一些極端的設計。假設我們的業務需求非常單一,只需要某些特定的Cuboid,那麼可以建立多個聚合組,每個聚合組代表一個Cuboid。具體的方法是在聚合組中先包含某個Cuboid所需的所有維度,然後把這些維度都設定為強制維度。這樣當前的聚合組就只能產生我們想要的那一個Cuboid了。
再比如,有的時候我們的Cube中有一些基數非常大的維度,如果不做特殊處理,它就會和其他的維度進行各種組合,從而產生一大堆包含它的Cuboid。包含高基數維度的Cuboid在行數和體積上往往非常龐大,這會導致整個Cube的膨脹率變大。如果根據業務需求知道這個高基數的維度只會與若干個維度(而不是所有維度)同時被查詢到,那麼就可以通過聚合組對這個高基數維度做一定的“隔離”。我們把這個高基數的維度放入一個單獨的聚合組,再把所有可能會與這個高基數維度一起被查詢到的其他維度也放進來。這樣,這個高基數的維度就被“隔離”在一個聚合組中了,所有不會與它一起被查詢到的維度都沒有和它一起出現在任何一個分組中,因此也就不會有多餘的Cuboid產生。這點也大大減少了包含該高基數維度的Cuboid的數量,可以有效地控制Cube的膨脹率。
2.2 併發粒度優化
當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倍的併發量。