優化Cube
層次結構
理論上,對於N維,你最終會得到2 ^ N維組合。但是對於某些維度組,不需要創建這么多組合。例如,如果您有三個維度:洲,國家,城市(在層次結構中,“更大”維度首先出現)。在深入分析時,您只需要以下三種組合組合:
按大陸分組
按大陸,國家分組
按大陸,國家,城市分組
在這種情況下,組合計數從2 ^ 3 = 8減少到3,這是一個很好的優化。 YEAR,QUATER,MONTH,DATE案例也是如此。
派生列
派生列用於一個或多個維度(它們必須是查找表上的維度,這些列稱為“派生”)可以從另一個維度推導出來(通常它是相應的FK,這稱為“主機列”)
例如,假設我們有一個查找表,我們將其連接到事實表,並將其與“其中DimA = DimX”。請注意,在Kylin中,如果您選擇FK為維度,相應的PK將自動排隊,無需任何額外費用。秘訣是,由於FK和PK總是相同的,Kylin可以先在FK上應用過濾器/ groupby,然后將它們透明地替換為PK。這表明如果我們想在我們的立方體中使用DimA(FK),DimX(PK),DimB,DimC,我們可以安全地選擇DimA,DimB,DimC。
事實表(連接)查找表
column1,column2 ,,,,,, DimA(FK)DimX(PK),, DimB,DimC
假設DimA(代表FK / PK的維度)具有到DimB的特殊映射:
dimA dimB dimC
1 a ?
2 b ?
3 c ?
4 a ?
在這種情況下,給定DimA中的值,確定DimB的值,因此我們說dimB可以從DimA導出。當我們構建一個包含DimA和DimB的多維數據集時,我們簡單地包含DimA,並將DimB標記為派生。派生列(DimB)不參與長方體生成:
原創組合:
ABC,AB,AC,BC,A,B,C
從A到B時的組合:
AC,A,C
在運行時,如果查詢類似於“select count(*) from fact_table inner join looup1 group by looup1 .dimB”,則期望包含DimB的長方體來回答查詢。但是,由於派生優化,DimB將出現在NONE的長方體中。在這種情況下,我們首先修改執行計划以使其由DimA(其主機列)進行分組,我們將得到如下的中間答案:
DIMA COUNT(*)
1 1
2 1
3 1
4 1
之后,Kylin將用DimB值替換DimA值(因為它們的值都在查找表中,Kylin可以將整個查找表加載到內存中並為它們構建映射),並且中間結果變為:
DimB count(*)
a 1
b 1
c 1
a 1
在此之后,運行時SQL引擎將進一步將中間結果聚合為:
DimB count(*)
a 2
b 1
c 1
這一步發生在查詢運行時,這意味着“以額外的運行時聚合為代價”
性能優化
分區列優化
如果cube的分區列與Hive表的分區列相同,那么根據它過濾數據能讓Hive聰明地跳過不匹配的分區。因此強烈建議用Hive的分區列(如果它是日期列)作為cube的分區列。這對於那些數據量很大的表來說幾乎是必須的,否則Hive不得不每次在這步掃描全部文件,消耗非常長的時間。
文件合並
如果啟用了Hive的文件合並,你可以在conf/kylin_hive_conf.xml里關閉它,因為Kylin有自己合並文件的方法(下一節):
<property>
<name>hive.merge.mapfiles</name>
<value>false</value>
<description>Disable Hive's auto merge</description>
</property>
重新分發中間表
Hive在HDFS上的目錄里生成了數據文件:有些是大文件,有些是小文件甚至空文件。這種不平衡的文件分布會導致之后的MR任務出現數據傾斜的問題:有些mapper完成得很快,但其他的就很慢。針對這個問題,Kylin增加了這一個步驟來“重新分發”數據,這是示例輸出:
total input rows = 159869711
expected input rows per mapper = 1000000
num reducers for RedistributeFlatHiveTableStep = 160
重新分發表的命令:
hive -e "USE default;
SET dfs.replication=2;
SET hive.exec.compress.output=true;
SET hive.auto.convert.join.noconditionaltask=true;
SET hive.auto.convert.join.noconditionaltask.size=100000000;
SET mapreduce.job.split.metainfo.maxsize=-1;
set mapreduce.job.reduces=160;
set hive.merge.mapredfiles=false;
INSERT OVERWRITE TABLE kylin_intermediate_airline_cube_v3610f668a3cdb437e8373c034430f6c34 SELECT * FROM kylin_intermediate_airline_cube_v3610f668a3cdb437e8373c034430f6c34 DISTRIBUTE BY RAND();"
首先,Kylin計算出中間表的行數,然后基於行數的大小算出重新分發數據需要的文件數。默認情況下,Kylin為每一百萬行分配一個文件。在這個例子中,有1.6億行和160個reducer,每個reducer會寫一個文件。在接下來對這張表進行的MR步驟里,Hadoop會啟動和文件相同數量的mapper來處理數據(通常一百萬行數據比一個HDFS數據塊要小)。如果你的日常數據量沒有這么大或者Hadoop集群有足夠的資源,你或許想要更多的並發數,這時可以將conf/kylin.properties
里以下配置設為小一點的數值,比如:
kylin.job.mapreduce.mapper.input.rows=500000
其次,Kylin會運行 “INSERT OVERWRITE TABLE … DISTRIBUTE BY “ 形式的HiveQL來分發數據到指定數量的reducer上。在很多情況下,Kylin請求Hive隨機分發數據到reducer,然后得到大小相近的文件,分發的語句是”DISTRIBUTE BY RAND()”。
如果你的cube指定了一個高基數的列,比如”USER_ID”,作為”分片”維度(在cube的“高級設置”頁面),Kylin會讓Hive根據該列的值重新分發數據,那么在該列有着相同值的行將被分發到同一個文件。這比隨機要分發要好得多,因為不僅重新分布了數據,並且在沒有額外代價的情況下對數據進行了預先分類,如此一來接下來的cube build處理會從中受益。在典型的場景下,這樣優化可以減少40%的build時長。在這個案例中分發的語句是”DISTRIBUTE BY USER_ID”:
請注意: 1)“分片”列應該是高基數的維度列,並且它會出現在很多的cuboid中(不只是出現在少數的cuboid)。 使用它來合理進行分發可以在每個時間范圍內的數據均勻分布,否則會造成數據傾斜,從而降低build效率。典型的正面例子是:“USER_ID”、“SELLER_ID”、“PRODUCT”、“CELL_NUMBER”等等,這些列的基數應該大於一千(遠大於reducer的數量)。 2)”分片”對cube的存儲同樣有好處,不過這超出了本文的范圍。
將cuboid數據轉換為HFile
這一步啟動一個MR任務來講cuboid文件(序列文件格式)轉換為HBase的HFile格式。Kylin通過cube統計數據計算HBase的region數目,默認情況下每5GB數據對應一個region。Region越多,MR使用的reducer也會越多。如果你觀察到reducer數目較小且性能較差,你可以將“conf/kylin.properties”里的以下參數設小一點,比如:
kylin.hbase.region.cut=2
kylin.hbase.hfile.size.gb=1
rowkey構建
對rowkey的構建也有一定的要求,一般而言,需要把基數大的字段放在前面,這樣可以在scan的過程中盡可能的跳過更多的rowkey。
另一方面將基數小的列放在rowkey的后面,可以減少構建的重復計算,有些cuboid可以通過一個以上的父cuboid聚合而成,在這種情況下,Kylin將會選擇最小的父cuboid。例如,AB能夠通過ABC(id:1110)和ABD(id:1101)聚合生成,因此ABD會被作為父cuboid使用,因為它的id比ABC要小。基於以上處理,如果D的基數很小,那么此次聚合操作就會花費很小的代價。因此,當設計cube的rowkey順序的時候,請記住,將低基數的維度列放在尾部。這不僅對cube的構建過程有好處,而且對cube查詢也有好處,因為后聚合(應該是指在HBase查找對應cuboid的過程)也遵循這個規則。
數據轉換為HFile
kylin將生成的cube通過生成HFile的方式導入到hbase,這個優化點可以配置hbase的相關參數。
- region數量默認是1,如果數據量大的話可以提高region數量
- region大小默認是5GB,也就是hbae官方建議的大小;如果cube大小比這個值小太多,可以減小單region的大小
- hfile文件大小,默認是1GB,由於是通過mapreduce寫入的,小文件意味着寫入快,但是讀取慢,大文件意味着寫入慢,讀取快
經驗
- 盡量將需要展現的字段作為維度,沒必要所有的一股腦加進去。
- 每次查詢或者要經常group by的字段作為Mandatory維度。且該維度放在 rowkey的最前面。
- 將數量相近也就是說某兩個字段通過select count("字段名")獲取的結果近似1:1,設置為joint維度。
- rowkey的順序按查詢頻率從高到低,從前往后排。
- 將經常出現在同一SQL中的不同維度放置在一個維度組中,將從不出現在一個SQL查詢中的不同維度設置在不同的維度組中。
- Dictionary默認為dict類型,如果某個字段中的值非常大(小幽遇到過的一個字段中的值保存成文本足足有23Kb!!!),大到以至或者可能使得Cube在build過程中出現OOM的錯誤,則需要將該字段的值設置為fixed_length類型,取可以展現這個維度的前length個字節,比如對於之前那個23kb的字段值,經和業務人員協商,發現取前4000個字節就可以表示這個字段了。所以fixed_length的值設置為4000.值得一提的是,Dictionary默認為false,是不給該字段在內存中建立詞典樹的,而更改為true則表示給該字段建立詞典樹。有詞典樹,則會優化帶有該字段的SQL查詢,提升查詢速度,但相應地也會消耗一些內存。
總結
基於kylin的ui,可以看到kylin在構建cube時各個流程的耗時,可以依據這些耗時做相應的優化,常見的,可以從耗時最長的步驟開始優化,比如:
- 遇到創建hive中間表時間很長,考慮對hive表進行分區處理,對表中的文件格式更改,使用orc,parquet等高性能的文件格式
- 遇到cube構建時間過長,查看cube設計是否合理,維度的組合關系是否可以再減少,構建引擎是否可以優化
分享一個其他得cube優化設計的推薦:https://www.cnblogs.com/wenBlog/p/10255467.html