Spark 3.0 - AQE淺析 (Adaptive Query Execution)

1、前言

近些年來,在對Spark SQL優化上,CBO是最成功的一個特性之一。
CBO會計算一些和業務數據相關的統計數據,來優化查詢,例如行數、去重後的行數、空值、最大最小值等。
Spark根據這些數據,自動選擇BHJ或者SMJ,對於多Join場景下的Cost-based Join Reorder(可以參考之前寫的這篇文章),來達到優化執行計劃的目的。
但是,由於這些統計數據是需要預先處理的,會過時,所以我們在用過時的數據進行判斷,在某些情況下反而會變成負面效果,拉低了SQL執行效率。
AQE在執行過程中統計數據,並動態地調節執行計劃,從而解決了這個問題。

2、框架

對於AQE而言,最重要的問題就是什麼時候去重新計算優化執行計劃。Spark任務的算子如果管道排列,依次並行執行。然而,shuffle或者broadcast exchange會打斷算子的排列執行,我們稱其爲物化點(Materialization Points),並且用"Query Stages"來代表那些被物化點所分割的小片段。每個Query Stage會產出中間結果,當且僅當該stage及其並行的所有stage都執行完成後,下游的Query Stage才能被執行。所以當上遊部分stage執行完成,partitions的統計數據也獲取到了,並且下游還未開始執行,這就給AQE提供了reoptimization的機會。
在這裏插入圖片描述
在查詢開始時,生成完了執行計劃,AQE框架首先會找到並執行那些不存在上游的stages。一旦這些stage有一個或多個完成,AQE框架就會將其在physical plan中標記爲完成,並根據已完成的stages提供的執行數據來更新整個logical plan。基於這些新產出的統計數據,AQE框架會執行optimizer,根據一系列的優化規則來進行優化;AQE框架還會執行生成普通physical plan的optimizer以及自適應執行專屬的優化規則,例如分區合併、數據傾斜處理等。於是,我們就獲得了最新優化過的執行計劃和一些已經執行完成的stages,至此爲一次循環。接着我們只需要繼續重複上面的步驟,直到整個query都跑完。

在Spark 3.0中,AQE框架擁有三大特徵:

  • 動態摺疊shuffle過程中的partition
  • 動態選擇join策略
  • 動態優化存在數據傾斜的join

接下來我們就具體來看看這三大特徵。

① 動態合併shuffle partitions

在我們處理的數據量級非常大時,shuffle通常來說是最影響性能的。因爲shuffle是一個非常耗時的算子,它需要通過網絡移動數據,分發給下游算子。

在shuffle中,partition的數量十分關鍵。partition的最佳數量取決於數據,而數據大小在不同的query不同stage都會有很大的差異,所以很難去確定一個具體的數目:

  • 如果partition過少,每個partition數據量就會過多,可能就會導致大量數據要落到磁盤上,從而拖慢了查詢。
  • 如果partition過多,每個partition數據量就會很少,就會產生很多額外的網絡開銷,並且影響Spark task scheduler,從而拖慢查詢。

爲了解決該問題,我們在最開始設置相對較大的shuffle partition個數,通過執行過程中shuffle文件的數據來合併相鄰的小partitions。
例如,假設我們執行SELECT max(i) FROM tbl GROUP BY j,表tbl只有2個partition並且數據量非常小。我們將初始shuffle partition設爲5,因此在分組後會出現5個partitions。若不進行AQE優化,會產生5個tasks來做聚合結果,事實上有3個partitions數據量是非常小的。在這裏插入圖片描述
然而在這種情況下,AQE只會生成3個reduce task。

在這裏插入圖片描述

② 動態切換join策略

在Spark所支持的衆多join中,broadcast hash join性能是最好的。因此,如果需要廣播的表的預估大小小於了廣播限制閾值,那麼我們就應該將其設爲BHJ。但是,對於表的大小估計不當會導致決策錯誤,比如join表有很多的filter(容易把表估大)或者join表有很多其他算子(容易把表估小),而不僅僅是全量掃描一張表。

由於AQE擁有精確的上游統計數據,因此可以解決該問題。比如下面這個例子,右表的實際大小爲15M,而在該場景下,經過filter過濾後,實際參與join的數據大小爲8M,小於了默認broadcast閾值10M,應該被廣播。

在這裏插入圖片描述
在我們執行過程中轉化爲BHJ的同時,我們甚至可以將傳統shuffle優化爲本地shuffle(例如shuffle讀在mapper而不是基於reducer)來減小網絡開銷。

③ 動態優化數據傾斜

數據傾斜是由於集羣上數據在分區之間分佈不均勻所導致的,它會拉慢join場景下整個查詢。AQE根據shuffle文件統計數據自動檢測傾斜數據,將那些傾斜的分區打散成小的子分區,然後各自進行join。

我們可以看下這個場景,Table A join Table B,其中Table A的partition A0數據遠大於其他分區。
在這裏插入圖片描述
AQE會將partition A0切分成2個子分區,並且讓他們獨自和Table B的partition B0進行join。

在這裏插入圖片描述
如果不做這個優化,SMJ將會產生4個tasks並且其中一個執行時間遠大於其他。經優化,這個join將會有5個tasks,但每個task執行耗時差不多相同,因此個整個查詢帶來了更好的性能。

3、使用

我們可以設置參數spark.sql.adaptive.enabled爲true來開啓AQE,在Spark 3.0中默認是false,並滿足以下條件:

  • 非流式查詢
  • 包含至少一個exchange(如join、聚合、窗口算子)或者一個子查詢

AQE通過減少了對靜態統計數據的依賴,成功解決了Spark CBO的一個難以處理的trade off(生成統計數據的開銷和查詢耗時)以及數據精度問題。相比之前具有侷限性的CBO,現在就顯得非常靈活 - 我們再也不需要提前去分析數據了!

本文參考Adaptive Query Execution: Speeding Up Spark SQL at Runtime

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章