前言
Catalyst是Spark SQL核心優化器,早期主要基於規則的優化器RBO,後期又引入基於代價進行優化的CBO。但是在這些版本中,Spark SQL執行計劃一旦確定就不會改變。由於缺乏或者不準確的資料統計資訊(如行數、不同值的數量、NULL值、最大/最小值等)和對成本的錯誤估算導致生成的初始計劃不理想,從而導致執行效率相對低下。
那麼就引來一個思考:我們如何能夠在執行時獲取更多的執行資訊,然後根據這些資訊來動態調整並選擇一個更優的執行計劃呢?
Spark SQL自適應執行優化引擎(Adaptive Query Execution,簡稱AQE)應運而生,它可以根據執行過程中的中間資料優化後續執行,從而提高整體執行效率。核心在於:通過在執行時對查詢執行計劃進行優化,允許Spark Planner在執行時執行可選的執行計劃,這些計劃將基於執行時統計資料進行優化,從而提升效能。
AQE完全基於精確的執行時統計資訊進行優化,引入了一個基本的概念Query Stages,並且以Query Stage為粒度,進行執行時的優化,其工作原理如下所示:
-
由shuffle和broadcast exchange把查詢執行計劃分為多個query stage,query stage執行完成時獲取中間結果
- query stage邊界是執行時優化的最佳時機(天然的執行間歇;分割槽、資料大小等統計資訊已經產生)
整個AQE的工作原理以及流程為:
- 執行沒有依賴的stage
- 在一個stage完成時再依據新的統計資訊優化剩餘部分
- 執行其他已經滿足依賴的stage
- 重複步驟(2)(3)直至所有stage執行完成
Spark從2.3版本,就開始"試驗"SparkSQL自適應查詢執行功能(Adaptive Query Execution),並在Spark3.0正式釋出。
自適應查詢執行框架(AQE)
自適應查詢執行最重要的問題之一是何時進行重新優化。Spark運算元通常是pipeline化的,並以並行的方式執行。然而shuffle或broadcast exchange會打破這個pipeline。我們稱它們為物化點,並使用術語"查詢階段"來表示查詢中由這些物化點限定的子部分。每個查詢階段都會物化它的中間結果,只有當執行物化的所有並行程式都完成時,才能繼續執行下一個階段。這為重新優化提供了一個絕佳的機會,因為此時所有分割槽上的資料統計都是可用的,並且後續操作還沒有開始。
當查詢開始時,自適應查詢執行框架首先啟動所有葉子階段(leaf stages)—— 這些階段不依賴於任何其他階段。一旦其中一個或多個階段完成物化,框架便會在物理查詢計劃中將它們標記為完成,並相應地更新邏輯查詢計劃,同時從完成的階段檢索執行時統計資訊。
基於這些新的統計資訊,框架將執行優化程式、物理計劃程式以及物理優化規則,其中包括常規物理規則(regular physical rules)和自適應執行特定的規則,如coalescing partitions(合併分割槽)、skew join handling(join資料傾斜處理)等。現在我們有了一個新優化的查詢計劃,其中包含一些已完成的階段,自適應執行框架將搜尋並執行子階段已全部物化的新查詢階段,並重覆上面的execute-reoptimize-execute過程,直到完成整個查詢。
在Spark 3.0中,AQE框架帶來了以下三個特性:
- Dynamically coalescing shuffle partitions(動態合併shuffle的分割槽)可以簡化甚至避免調整shuffle分割槽的數量。使用者可以在開始時設定相對較多的shuffle分割槽數,AQE會在執行時將相鄰的小分割槽合併為較大的分割槽
- Dynamically switching join strategies(動態調整join策略)在一定程度上避免由於缺少統計資訊或著錯誤估計大小(當然也可能兩種情況同時存在),而導致執行次優計劃的情況。這種自適應優化可以在執行時sort merge join轉換成broadcast hash join,從而進一步提升效能
- Dynamically optimizing skew joins(動態優化資料傾斜的join)skew joins可能導致負載的極端不平衡,並嚴重降低效能。在AQE從shuffle檔案統計資訊中檢測到任何傾斜後,它可以將傾斜的分割槽分割成更小的分割槽,並將它們與另一側的相應分割槽連線起來。這種優化可以並行化傾斜處理,獲得更好的整體效能。
下面我們來詳細介紹這三個特性。
動態合併shuffle的分割槽
當在Spark中執行查詢來處理非常大的資料時,shuffle通常對查詢效能有非常重要的影響。shuffle是一個昂貴的操作,因為它需要在網路中移動資料,以便資料按照下游操作所要求的方式重新分佈。
分割槽的數量是shuffle的一個關鍵屬性。分割槽的最佳數量取決於資料,但是資料大小可能在不同的階段、不同的查詢之間有很大的差異,這使得這個分割槽數很難調優:
- 如果分割槽數太少,那麼每個分割槽處理的資料可能非常大,處理這些大分割槽的任務可能需要將資料溢寫到磁碟(例如,涉及排序或聚合的操作),從而減慢查詢速度
- 如果分割槽數太多,那麼每個分割槽處理的資料可能非常小,並且將有大量的網路資料獲取來讀取shuffle塊,這也會由於低效的I/O模式而減慢查詢速度。大量的task也會給Spark任務排程程式帶來更多的負擔
為了解決這個問題,我們可以在開始時設定相對較多的shuffle分割槽數,然後在執行時通過檢視shuffle檔案統計資訊將相鄰的小分割槽合併為較大的分割槽。
假設我們執行如下SQL:
SELECT max(i)FROM tbl GROUP BY j
tbl表的輸入資料相當小,所以在分組之前只有兩個分割槽。我們把初始的shuffle分割槽數設定為5,因此在shuffle的時候資料被打亂到5個分割槽中。如果沒有AQE,Spark將啟動5個task來完成最後的聚合。然而,這裡有三個非常小的分割槽,為每個分割槽啟動一個單獨的task將是一種浪費。
使用AQE之後,Spark將這三個小分割槽合併為一個,因此,最終的聚合只需要執行3個task,而不是5個task。
動態調整join策略
Spark支援多種join策略(如broadcast hash join、shuffle hash join、sort merge join),通常broadcast hash join是效能最好的,前提是參與join的一張表的資料能夠裝入記憶體。由於這個原因,當Spark估計參與join的表資料量小於廣播大小的閾值時,它會將join策略調整為broadcast hash join。但是,很多情況都可能導致這種大小估計出錯——例如存在一個非常有選擇性的過濾器。
為了解決這個問題,AQE現在根據最精確的連線關係大小在執行時重新規劃join策略。在下面的示例中可以看到join的右側比估計值小得多,並且小到足以進行廣播,因此在AQE重新優化之後,靜態計劃的sort merge join會被轉換為broadcast hash join。
對於在執行時轉換的broadcast hash join,我們可以進一步將常規的shuffle優化為本地化shuffle來減少網路流量。
動態優化資料傾斜的join
當資料在叢集中的分割槽之間分佈不均時,就會發生資料傾斜。嚴重的傾斜會顯著降低查詢效能,特別是在進行join操作時。AQE傾斜join優化從shuffle檔案統計資訊中自動檢測到這種傾斜。然後,它將傾斜的分割槽分割成更小的子分割槽,這些子分割槽將分別從另一端連線到相應的分割槽。
假設表A join 表B,其中表A的分割槽A0裡面的資料明顯大於其他分割槽。
skew join optimization將把分割槽A0分成兩個子分割槽,並將每個子分割槽join表B的相應分割槽B0。
如果沒有這個優化,將有四個任務執行sort merge join,其中一個任務將花費非常長的時間。在此優化之後,將有5個任務執行join,但每個任務將花費大致相同的時間,從而獲得總體更好的效能。
AQE查詢計劃
AQE查詢計劃的一個主要區別是,它通常隨著執行的進展而演變。引入了幾個AQE特定的計劃節點,以提供有關執行的更多詳細資訊。
此外,AQE使用了一種新的查詢計劃字串格式,可以顯示初始和最終的查詢執行計劃。
|| AdaptiveSparkPlan節點
應用了AQE的查詢通常有一個或多個AdaptiveSparkPlan節點作為每個查詢或子查詢的root節點。在執行之前或期間,isFinalPlan標誌將顯示為false。查詢完成後,此標誌將變為true,並且AdaptiveSparkPlan節點下的計劃將不再變化。
|| CustomShuffleReader節點
CustomShuffleReader節點是AQE優化的關鍵。它可以根據在shuffle map stage收集的統計資訊動態調整shuffle後的分割槽數。在Spark UI中,使用者可以將滑鼠懸停在該節點上,以檢視它應用於無序分割槽的優化。
當CustomShuffleReader的標誌為coalesced時,表示AQE已根據目標分割槽大小在shuffle後檢測併合並了小分割槽。此節點的詳細資訊顯示合併後的無序分割槽數和分割槽大小。
當CustomShuffleReader的標誌為"skewed"時,這意味著AQE在排序合併連線操作之前檢測到一個或多個分割槽中的資料傾斜。此節點的詳細資訊顯示了傾斜分割槽的數量以及從傾斜分割槽拆分的新分割槽的總數。
coalesced和skewed也可以同時發生:
|| 檢測join策略改變
通過比較AQE優化前後查詢計劃join節點的變化,可以識別join策略的變化。在dbr7.3中,AQE查詢計劃字串將包括初始計劃(應用任何AQE優化之前的計劃)和當前或最終計劃。這樣可以更好地瞭解應用於查詢的優化AQE。
Spark UI將只顯示當前計劃。為了檢視使用Spark UI的效果,使用者可以比較查詢執行之前和執行完成後的計劃圖:
|| 檢測傾斜join
傾斜連線優化的效果可以通過連線節點名來識別。
在Spark UI中:
在查詢計劃字串中:
AQE的TPC-DS表現
在我們使用TPC-DS資料和查詢的實驗中,自適應查詢執行的查詢效能提高了8倍,32個查詢的效能提高了1.1倍以上。下面是通過AQE獲得的10個TPC-DS查詢效能提高最多的圖表。
這些改進大部分來自動態分割槽合併和動態join策略調整,因為隨機生成的TPC-DS資料沒有傾斜。在實際生產中,AQE 帶來了更大的效能提升。
啟用AQE
可以通過設定引數spark.sql.adaptive為true來啟用AQE(在Spark3.0中預設為false)。
如果查詢滿足以下條件建議啟用:
- 不是一個流查詢
- 至少包含一個exchange(通常在有join、聚合或視窗操作時)或是一個子查詢
通過減少查詢優化對靜態統計的依賴,AQE解決了Spark基於成本優化的最大難題之一:統計資訊收集開銷和估計精度之間的平衡。
為了獲得最佳的估計精度和規劃結果,通常需要維護詳細的、最新的統計資訊,其中一些統計資訊的收整合本很高,比如列直方圖,它可用於提高選擇性和基數估計或檢測資料傾斜。AQE在很大程度上消除了對此類統計資料的需要,以及手動調優工作的需要。
除此之外,AQE還使SQL查詢優化對於任意udf和不可預測的資料集更改(例如資料大小的突然增加或減少、頻繁的和隨機的資料傾斜等)更有彈性。不再需要提前"知道"您的資料。隨著查詢的執行,AQE將計算出資料並改進查詢計劃,提高查詢效能以獲得更快的分析和系統效能。
本文主要參譯自:
1.https://databricks.com/blog/2020/05/29/adaptive-query-execution-speeding-up-spark-sql-at-runtime.html
2.https://databricks.com/blog/2020/10/21/faster-sql-adaptive-query-execution-in-databricks.html
關於Spark3.0更多特性,感興趣的同學建議去Spark官網和Databricks官方部落格學習。
關於AQE也可以參考:
https://my.oschina.net/hblt147/blog/3006406
https://www.cnblogs.com/zz-ksw/p/11254294.html
https://issues.apache.org/jira/browse/SPARK-23128
推薦文章:
Apache Spark 3.0.0重磅釋出 —— 重要特性全面解析
Spark在處理資料的時候,會將資料都載入到記憶體再做處理嗎?
SparkSQL中產生笛卡爾積的幾種典型場景以及處理策略
Spark SQL如何選擇join策略
Spark實現推薦系統中的相似度演算法
Spark閉包 | driver & executor程式程式碼執行
Spark SQL | 目前Spark社群最活躍的元件之一
關注大資料學習與分享,獲取更多技術乾貨