改造mondrian的構想,改造mondrian構想
項目中最近需要使用mondrian,於是開始首先閱讀以下mondrian的原始碼,總體看來mondrian是一個ROLAP引擎,但是它的定位還是一個以庫形式 提供的組件,而不是作為服務,例如它將所有的緩衝都放在進程內部,當我在一個進程中建立多個cube並且一直保持著他們的connection會導致OOM,所以對mondrian作為服務化的首要改造還是在於如何將mondrian的緩衝挪出本進程,放到第三方的儲存系統例如redis、hbase等NOSQL儲存系統中,這樣一方面可以解決上面提到的進程內緩衝問題,另一方面可以將資料獨立出去可以將伺服器做成無狀態的,可以水平擴充,保證高可用性。除了緩衝這一步之外,另外需要對mondrian做最佳化的地方還有產生SQL這一部分,mondrian產生的SQL主要包括查看一個level下所有的member和查看一個cell結果這兩類,前者無非就是select distinct xxx或者select xxx group by xxx,mondrian一般使用select distinct的方式,這種沒有什麼可以最佳化的地方,另外還有一種查詢是查詢多個維度彙總之後的某一個度量的值,一般是多個維度group by之後計算某一列的sum、avg等值,但是如果在group by之前需要過濾掉一些元素的話,mondrian會將剩餘的語句翻譯成where xxx IN (xxx1, xxx2,...)(通過執行日誌可以看到),這種SQL執行效率是比較低的,即使xxx這一列有主鍵,改造的方案是儘可能得將IN語句改成多個between或者大於小於這樣的SQL,這個的優先順序可以低一些。
上面提到的是對mondrian本身不足需要進行改造的地方,但是像mondrian這樣的一個ROLAP系統的執行效能的確不怎麼讓人滿意,業界做的一些OLAP引擎(例如百度的palo、ebay的kylin等)總是產生可以再查詢速度秒級,而mondrian的緩衝很渣,每次如果再去資料來源執行SQL又會很慢,因此我們對這個效能還需要特別注意,polo自己做了資料倉儲,所有需要查詢的資料都由該系統自己維護,因此做效能最佳化也是很容易的,而mondrian使用的資料在資料來源(關係型資料庫或者大資料系統中)儲存,我們並不能改變它儲存的形式,因此大部分的效能還是取決於資料來源的效能(是否加主鍵,是否使用SSD儲存,hadoop叢集節點數等等),基於這個限制我覺得如果想要對效能有質的提升也只有一條路,緩衝,緩衝,緩衝,重要的事情說三遍,但是畢竟緩衝一個cube的緩衝量還是太大了(下面粗略的計算一下一個cube需要的緩衝量),其實如果我們有一個足夠大的緩衝,我們理論上也就是自己做了資料倉儲,這方面還是可以考慮的,既然自己可以使用緩衝作為資料倉儲,那麼就需要做類似ETL操作,預先將使用者的資料載入到緩衝中,也就是所謂的cube預計算,但是和真正的資料倉儲不一樣的地方在於,資料倉儲儲存的全域的需要分析的資料,而預計算的只是針對一個cube所能夠使用的資料,目前來看,Kylin的預計算方式是值得我們學習的,但是Kylin主要解決的問題還是基於hadoop系統下的資料分析問題(資料使用hadoop儲存,使用hive查詢的),由於hive的速度實在是讓人難以接受(尤其是負載的OLAP查詢產生的SQL),因此做預計算並且將結果緩衝到hbase是非常有必要的,但是既然我們使用mondrian這樣的引擎就是希望我們的資料來源既可以是關聯式資料庫還可以是hive之類的資料庫,甚至使用hbase(phoenix提供SQL介面),這裡的預計算對於關聯式資料庫是否必要就值得考慮一下了。
既然做了預計算,也就是做緩衝,無法避免的問題就是緩衝一致性問題了,如何保證在資料量更新的情況下cube的快取資料也能更新到最新的狀態,當然對於OLAP系統來說資料的即時性不是最重要的,一般情況下幾個小時更新一次資料或者一天更新一次資料也是可以接受的,而更新資料來源之後也不要求cube的資料能夠立即更新,能夠達到最終一致性的要求也是可以接受的。但是當資料來源匯入新的資料的時候可能導致整個cube的值都發生改變,如果快速的計算新的cell值也是需要解決的問題,當前看到的一些系統都是用了倒排索引(還不瞭解)的方式來實現增量計算,具體的實現可以參考一下Kylin的代碼。
接下來的任務和方向主要是熟悉mondrian和kylin兩個系統的實現,能夠基於他們實現出我們自己的面向mdx查詢和sql查詢的OLAP引擎(目前主要的方向還是mdx),但是mondrian對於mdx的支援還是有一定的缺陷的,最主要的問題就是它目前不支援子查詢(難道考慮到中間結果過大問題),因此一些進階的過濾操作目前還是實現不了,例如取出按照年份彙總之後總銷量小於1W的所有年份,然後對過濾之後的資料按照其他維度進行mdx查詢,這其實相當於對資料來源進行一定的過濾,首先使用mdx查出需要過濾的年份,然後在資料來源中刪除所有年份等於這些的資料(相當於每次查詢在源事實表的基礎上加了一個where條件),如果在外層實現這樣的過濾又太過麻煩,因此對於這樣的功能性的缺失目前還不知道如何解決。另外mondrian在定義事實表的時候不僅僅可以支援指定一個table,還可以指定任意的視圖,這也就以為這任意的SQL語句的結果都可以作為事實表,使用這種方式可以滿足上面的需求但是對於每次查詢翻譯成的SQL都需要多層的子查詢,效能可想而知了。後期我們如果能夠緩衝整個cube的時候可以直接才cube的基礎上滿足這樣的需求。
另外,mondrian裡面的維度是具有層級關係的,當我們定義了一個月層級,它的父層級是年,那麼月層級的成員需要攜帶上具體的年份,這時候如果只按照月份進行彙總統計的時候需要得到的結果是[1997].[1],[1998].[1]這樣的值,而不是[1],[2]這樣的結果,如果實現這樣的結果還需要將每一個維度層級進行拆分,這樣無疑是非常複雜的,不知道mdx裡面有沒有什麼關鍵字可以在指定一個level的是打破層架關係。
接下來就要深入看一下mondrian的代碼了,其實mondrian的主要介面也就兩個,首先是建立connection,其中包括載入cube的過程,另外就是執行一次mdx查詢,查詢的流程相對比較複雜,涉及到緩衝以及如何產生SQL,另外對於mondrian當前緩衝的結構以及對緩衝的管理也需要重點關注。
最後粗略的算一下一個cube的大小,假設情境是這樣的一個星型結構,包括一個事實表和3個維度資料表,分別是時間,地區,產品資訊,其中時間維度分為三個層級,分別是年份,季度和月份,假設年份有10個成員,季度有4*10個成員,月份有10*4*3個成員,地區有三個層級,分別是國家,省份和城市三個層級,國家有100個成員,省份一共有1000個成員,城市有5000個成員;產品維度包含兩個層級,分別為產品分類和產品商標,前者有16個成員,後者一共有500個成員,那麼整個cube就是所有維度集合可能的彙總值,其中每一個維度包含一個特殊的成員ALL成員,這個成員屬於一個特殊的層級ALL層級(按照mondrian的思想),每一個維度下的ALL層級是最高的層級,時間維度的深度為4,地區維度深度為4,產品維度深度為3,那麼我們從最底層的層級進行組合,一共包含時間維度的最底層層級包含120個成員,最底層包含5000個成員,產品維度最底層層級包含500個成員,那麼可能的組合值就是120*5000*500=3億個組合元素,這是最底層的組合元素,這些所有的組合可以看成一個長為120,寬為500,高為5000的立方體,這3億個元素就是整個立方體,這個立方體中每一個單元裡麵包含每一個組合(月=xxx,城市=xxx和產品商標=xxx)的彙總值,一個cell包含所有的度量值,這樣整個立方體就建立起來了,這個立方體是全量的值,其他的組合值可以都可以通過將該立方體的某一個子立方作為一個cell進行計算,例如我們要計算年份為1997,國家為中國,產品類型為食品的銷售總額,那麼就相當於將1997年下的所有月份(12個)、中國的所有城市(假設100個)和產品類型為食品的所有商標(假設為50個)所有組合的彙總值,也就是12*100*50=60W個 cell進行組合成的新的cell作為返回的結果。如果我們想要緩衝整個cube,最好的情況下我們還是需要緩衝所有最低層級所有成員的組合(因為從高層級得不到低層級的資訊,除非從資料來源擷取),這個代價還是相當大的,一般情況下我們需要對成員進行建索引(為每一個成員制定下標),然後通過下標的組合作為key,度量值 的組合作為value進行緩衝,但是當資料來源哪怕插入一條資料都會改變整個cube中的大量的cell值,這個增量計算還是相當可怕的。
在不考慮增量計算的情況下,其實這個還是緩衝量和查詢速度的博弈,如果緩衝量不夠,勢必會緩衝一些高層級,這樣對於底層級的查詢就需要走資料來源,效能就差,如果查詢快取底層級成員的組合那麼所有的查詢都不需要再走資料來源,而是直接在記憶體中計算。當然最好的辦法還是需要判斷一下最常用的層級和每一個層級的成員個數,如果某一層級的成員個數過多則不適合緩衝,如果查詢頻率比較高的層級則更適合緩衝。
任務艱巨啊,還是先一步一個腳印的走吧,首先第一步將mondrian的緩衝移出程式並且考慮一下緩衝結構是否有比較再進行最佳化,第二步可以分析一下mondrian的執行SQL,看一下有沒有最佳化的空間,第三步是進行預計算儲存在緩衝中,當然這時候暫時不考慮到動態累加式更新,最後再考慮如何做到增量計算。
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。