導言

物化視圖作爲一種預計算的優化方式,廣泛應用於傳統數據庫中,如Oracle,MSSQL Server等。隨着大數據技術的普及,各類數倉及查詢引擎在業務中扮演着越來越重要的數據分析角色,而物化視圖作爲數據查詢的加速器,將極大增強用戶在數據分析工作中的使用體驗。本文將基於 SparkSQL(2.4.4) + Hive (2.3.6), 介紹物化視圖在SparkSQL中的實現及應用。

什麼是物化視圖

物化視圖主要用於預先計算並保存表連接或聚合等耗時較多的操作的結果,這樣,在執行查詢時,就可以避免進行這些耗時的操作,從而快速的得到結果。物化視圖使用查詢重寫(query rewrite)機制,不需要修改原有的查詢語句,引擎自動選擇合適的物化視圖進行查詢重寫,完全對應用透明。它和視圖的區別在於,物化視圖將存儲實際的數據,而視圖只是存儲SQL語句。使用物化視圖的基本流程爲:

  1. 創建物化視圖

  2. Query查詢

  3. 基於物化視圖,對Query進行查詢重寫,生成新的Query

  4. 基於新的Query進行查詢

如下圖,user,item,ui是3張表,先創建物化視圖mv,使用Query查詢時,將基於mv對Query進行重寫,生成新的基於物化視圖的Query,再進行查詢。這個例子中可以看到,在最終生成的Query裏,消除了所有的join操作,將3表join查詢轉換成了單表查詢。對於大數據查詢引擎來說,大表join將會產生shuffle過程,是造成查詢緩慢的瓶頸之一,這種轉換將極大的提升查詢效率。

物化視圖現狀及實踐目標

傳統數據庫,如Oracle,MSSQL Server等都已經支持物化視圖,但是在大數據領域裏,支持這類預計算優化的有Hive,Kylin,ClickHouse,其中只有Hive是基於物化視圖,而Kylin由於並非將數據存儲在原有數倉中,而是存儲在自定義的介質中(HBase,Parquet等),所以和物化視圖還是有很大的差別。基於使用方式,ClickHouse在使用時需要輸入物化視圖的表名,不能做到查詢時對用戶透明。

SparkSQL目前有哪些預計算相關的優化呢?

  • 已有實現:

    • Cache Table

    • Reuse Exchange (該優化重用同一個Query中相同的表掃描操作)

  • 各大公司的自有實現:

    • Relational cache (阿里巴巴)

    • eBay, 字節跳動等內部數據平臺,在各種場合的分享中,也提到了對SparkSQL進行了物化視圖的優化

  • Spark社區的相關JIRA(未實現):

    • SPARK-29038: SPIP: Support Spark Materialized View

    • SPARK-26764: [SPIP] Spark Relational Cache

    • SPARK-29059: [SPIP] Support for Hive Materialized Views in Spark SQL

基於目前現狀,我們希望能將物化視圖的優化功能加入到SparkSQL中,並使用Hive對相關元數據進行管理。從實現角度看,Hive的實現是基於Calcite,將Hive的plan轉換成Calcite的結構,使用Calcite的AbstractMaterializedViewRule進行優化後,再轉換回Hive的plan,最終提交給計算引擎進行計算。我們目標是借鑑Calcite的實現方式(基於參考文獻【1】),將物化視圖的優化整合進Spark Catalyst框架。不引入Calcite的優勢是,避免核心功能強依賴於第三方庫,便於後續改動及功能增強。

物化視圖設計詳解

物化視圖的功能將拆分爲2部分,分別是SparkSQL + Hive整合,Plan Rewrite,其中Plan Rewrite是作爲整個功能的核心模塊,接下來將分別對這2部分進行描述。

SparkSQL + Hive 整合

爲什麼選擇Hive2.3

因爲Hive是在2.3將物化視圖功能引入(官網裏顯示是 3.0.0版本才被引入),雖然這個版本對於物化視圖支持還不夠完善,但是我們所需要的相關元數據管理已經具備。目前SparkSQL對於Hive的支持只實現到Hive2.3(參考HiveClientImpl),當然,內部Hive3.0還未開始大規模使用也是原因之一。這裏需要注意的是,2.3和3.0版本最主要的區別是存儲Materialized View的字段不同,在2.3中是存儲在View Original Text,而在3.0是存儲在View Expanded Text。

基於Hive的物化視圖DDL命令,在SparkSQL中新增DDL命令,用來管理物化視圖,新增命令如下:

  • create materialized view

  • drop materialized view

  • alter materialized view enable/disable rewrite

  • alter materialized view rebuild

由於物化視圖本質是一種類型的表,所以desc 命令同樣適用,無需新增。

在Spark中,HiveShim中並未實現新的物化視圖的元數據接口,需要進行實現,實現後的整合如下圖:

來看一個實際的例子,使用如下Query創建物化視圖後,

使用desc命令展示物化視圖的元數據,結果如下圖:

需要關注的是,Table Type和View Original Text,和普通表相比存儲了物化視圖相關的信息。在整合完成後,SparkSQL和Hive對於物化視圖的操作就完全打通,創建等操作互相可見。

Plan Rewrite 設計

設計概覽

上圖展示了Plan Rewrite功能實現涉及的基本流程,其中,Materialized Optimizer作爲單獨節點接入到整個SparkSQL流程中,爲什麼不和其它優化規則一起併入Optimizer?最主要的原因是,單獨列出來可以使用explain命令對優化後的LogicalPlan進行檢查。物化視圖的優化涉及到大量的代碼,這樣做對於後續的debug也是很有幫助。再回到基本流程,圖的下半部分是關於物化視圖優化的具體步驟,而SessionCatalog那條路徑則是用來從Hive側獲取物化視圖的Query。

優化步驟簡介

關於優化步驟,簡單來說就是提取實際Query和物化視圖的相關信息,進行信息相互的匹配,匹配成功後生成新的Query,再將新的Query轉換成LogicalPlan並返回,如下圖所示:

由於涉及的細節比較多,上圖並未全部展開,對於圖中列出的信息,其中:

  • table: (x, t1), (t1, t1), (t2, t2):對於每個表,記錄(表名,表名)的鍵值對,如果有別名,則增加(別名,表名)鍵值對

  • output: (t2.id, t2.id), (c, count(1)):對於每個輸出列,保存信息方式如表名

  • equalPreds: (t1.id, (t2.id)), (t2.id,(t1.id)):(col1,(col2,col3))說明col1,col2,col3是相等的列

  • otherPreds: (x.id > 10):除了列相等以外的查詢條件

  • groupby: t1.id:groupBy字段

本文由於不會涉及到實現的細節,所以上述的數據結構僅用來讓大家瞭解在實現過程所用到的部分輔助數據結構。在獲取到相關信息後,將進行匹配及替換過程,最終生成新的Query並返回LogicalPlan。

優化過程中的問題

基於參考文獻【1】,物化視圖在優化過程中需要考慮到一系列問題,接下來將例舉其中的部分:

列相等問題

查詢:

物化視圖:

優化結果:

在上面例子中,查詢的輸出是db1.emps.deptno,物化視圖的輸出是db2.depts.deptno,但是由於都存在列相等條件db1.emps.deptno = db2.depts.deptno,所以這類場景是可以被優化的。

條件匹配問題

查詢:

物化視圖:

優化結果:

條件匹配問題的核心其實是物化視圖包含的數據是否包含所有查詢所需的數據,如果沒有,則優化失敗。

表達式匹配問題

查詢:

物化視圖:

優化結果:

條件匹配問題用來判斷查詢所需要的表達式,是否可以通過物化視圖的輸出列進行計算,查詢的表達式不僅包含輸出列,還有where語句中存在的表達式。

多表查詢問題

我們將通過2個例子來了解下這個問題:查詢:

物化視圖:

優化結果:

上述例子中,查詢的表的數量大於物化視圖,在優化後需要額外join不在物化視圖中的表。

查詢:

物化視圖:

優化結果: ?

在這個例子中,查詢的表的數量小於物化視圖,這個時候能優化嗎?由於SparkSQL不支持主外鍵模型,所以這個問題系統是無法判斷的,需要用戶進行判斷物化視圖的數據是否包含了查詢所需的所有數據。我們爲這種情況添加了相關參數,默認不支持,但是用戶可以根據需要自行開啓。

聚合函數問題

我們依然通過2個例子來了解下這個問題:

查詢:

物化視圖:

優化結果:

這個例子中,查詢和物化視圖的groupBy語句裏包含相同的字段,所以優化結果可以直接使用mv_db.testmv.c替換查詢裏的c1。

查詢:

物化視圖:

優化結果:

這個例子中,查詢和物化視圖的groupBy語句裏包含的字段,所以優化結果對於聚合函數需要做額外的處理。

多個物化視圖匹配問題

查詢:

物化視圖1:

物化視圖2:

物化視圖3:

物化視圖4(被選中):

優化結果:

這個例子展示了當多個物化視圖匹配時,會選擇較優的物化視圖進行優化,如何判斷較優目前僅比較優化結果裏join和groupby的數量,相同時再比較filter的數量。由於篇幅有限,這裏不再一一列出更爲細節的問題了。

物化視圖實戰

本節將基於TPC-DS(100G),Query17,對物化視圖的實戰能力進行一次測試。

測試用的查詢

由於物化視圖和查詢中同一個Table在from語句裏不能出現多次,所以我們對測試用的查詢做了一些調整,具體如下:

測試用的物化視圖

將創建2個物化視圖用來測試,一個是基於Kylin風格,另一個是更靈活的風格。

這裏需要注意的是物化視圖1裏的輸出列和groupby語句裏增加了d quarter name字段,由於這個變化,所以針對物化視圖1的查詢將變更爲:

邏輯計劃比較

下圖展現了3種情況下,最終的邏輯執行計劃,可以看到物化視圖優化後,從多表join已經變成了單表查詢,而物化視圖2比物化視圖1多了Aggregate運算,這個是由groupBy產生。

Spark UI統計比較

下圖展示了3種情況下,各自的執行job/stage,注意,測試時開啓了Spark Adaptive Execution特性。

性能數據比較

下圖展示了3種情況下,各自的耗時數據,其中Spark統計耗時是來自Spark UI,而這個數據向我們展示了物化視圖在查詢效率方面的可能性,明明計算只用了0.6s,爲什麼查詢需要6s?在追求更快的響應時間時,這些額外的消耗是否能優化呢?比如資源調度耗時等,這個也是作爲一個後續的優化方向。

物化視圖 VS Kylin

物化視圖屬於一種基於預計算方式的查詢優化,也是我們常說的空間換時間。在大數據領域,提到查詢預計算,Kylin肯定會被提及,該項目在各個領域中有了很多的成功應用案例。那麼物化視圖和Kylin相比,區別在哪裏呢?接下來將分別和Kylin的兩種結構,做一些比較。比較內容僅限於離線預計算及查詢,畢竟Kylin還支持適時建cube等功能。

Kylin on HBase

Kylin 物化視圖
查詢性能 計算量少時,毫秒級;計算量大時,由於有單點計算的瓶頸,毫秒降至秒級甚至分鐘 計算量少時,比Kylin慢至少1個數量級;計算量大時,接近甚至超過Kylin效率(計算量越大,由於分佈式計算的存在,Spark將越有優勢)
查詢靈活性 在建Cube時,必須將所有度量,維度信息都進行指定,如果要增加新的維度表,重新建Cube。Cube數據存儲在Hbase中,不可和Hive中的數據進行混合查詢。 物化視圖數據存儲在Hive中,支持和其它表進行聯合查詢。如果新增一張維度表,無需重構物化視圖。
運維成本 Cube數據存儲在HBase中,需要額外維護HBase。建Cube過程會涉及到Hive + Spark + Hbase,包括建Hive寬表,計算cube,生成字典等多個過程。 數據存儲在Hive中,無需額外維護其它組件。創建物化視圖使用Hive ddl或SparkSQL ddl,過程類似於CTAS。
預計算管理 具備UI界面進行Cube的管理,且對Cube創建具備剪枝等優化 無物化視圖管理UI,該功能也不太適合整合進SparkSQL中,用戶需自行維護

基於上述比較,物化視圖使用更爲靈活,方便,也易於維護,但是Kylin在查詢效率,特別是計算量不大的查詢(如,沒有distinct),有着巨大的優勢。對於這兩者的應用更多要依賴於實際使用場景,比如:

報表類的場景,維度度量都不會經常變化,前端對於延時要求又比較高,那Kylin是一種很好的選擇。在上節例子中,mv_100_t17就是模擬Kylin創建物化視圖,在目前的SparkSQL實現中,延遲和Kylin差距還是非常大,但是相信經過適當的優化,性能會有很大的提升。

數據探索類的場景,如果在探索的過程中經常會嘗試新的數據維度,那麼物化視圖就能更好的滿足這種靈活性的需求。可以想象下這樣的狀況,分別通過Kylin和物化視圖,對於多張事實表和維度表進行預計算,忽然在數據分析過程中,還想增加維度信息。對於Kylin來說,重新建Cube,而物化視圖則支持直接查詢。當預計算成本很高時,這樣的靈活性還是能給分析工作帶來便捷的。在上節的例子中,mv_100_t17_2這類物化視圖,就能很好的體現這種靈活性。

Kylin自帶Cube管理界面,便於用戶對Cube進行管理,而物化視圖目前需要用戶自行搭建管理平臺或使用腳本方式,這個在使用時也應該加以考慮。

Kylin on Parquet

從2020/04開始,Kylin社區開始逐步推進下一代架構,Kylin on Parquet,主要的原因是爲了解決HBase運維不便,單點計算等問題。在捨棄HBase後,Cube的存儲將使用Parquet,而查詢將使用Spark引擎。雖然該架構還在推進演變中,但相比Kylin on HBase,這個架構和SparkSQL物化視圖更爲接近,物化視圖也能將數據以Parquet格式存儲,查詢引擎使用的是SparkSQL。爲了保證低延時,Kylin對Spark進行了優化(比如,從yarn獲取資源後,不再釋放,消除資源申請耗時),而這類優化對於SparkSQL是通用的,相信物化視圖也能從中獲取收益。假如在延時上Kylin不能佔據絕對的優勢,那我認爲物化視圖帶來的靈活性是Kylin目前做不到的,畢竟cube的數據即使存儲在parquet中也不能和Hive中的數據進行交互。

總結

本文介紹了數據庫常用的優化方式,物化視圖,在SparkSQL這個流行的大數據查詢引擎上的相關實踐,包括了實現的架構,實現的簡介,實戰中的表現等。同時也和業界大佬Kylin進行了對比,展示了這類優化方式的適用場景。後續有計劃將該優化在Spark社區進行開源,和大家一起將SparkSQL打造成更好的大數據計算引擎。

參考資料

【1】Optimizing Queries Using Materialized Views: A Practical, Scalable Solution

淺談配置化的Ozone網絡拓撲結構

Ozone如何利用Multi-Raft優化寫入吞吐量

Spark在雲原生時代的發展

SparkSQL DatasourceV2 之 Multiple Catalog

數據湖,大數據的下一個變革!

歡迎關注我們公衆號

騰訊大數據誠招計算,存儲,消息中間件,調度,中臺等各方向的大數據研發工程師,請私信或聯繫[email protected]

相關文章