SQL優化器原理-Metadata

原文地址


這是MaxCompute有關SQL優化器原理的系列文章之一。我們會陸續推出SQL優化器有關優化規則和框架的其他文章。添加釘釘羣“關係代數優化技術”(羣號11719083)可以獲取最新文章發佈動態(二維碼在文章末尾)。

簡介

SQL是一種關係代數,在進行關係代數等價轉換時,我們利用Metadata獲得更多的上下文和數據信息,而從獲得更優的執行計劃。爲了進一步介紹Metadata如何讓優化器更加“Smart”,接下來會先介紹幾種使用Metadata的場景。

場景

SELECT trade_date, count(trade_id) cnt_of_trades FROM trades GROUP BY trade_date;

這個語句是想要以交易日爲單元,計算交易日內的交易筆數。而語句的執行效率(時間)依賴於數據的分佈情況。
假設收到的trades數據集合如圖1:
trade.1.png

爲了計算這個聚合操作,在分佈式數據處理系統中,會把trades數據分成多份。假設分成3份,由3個Worker進行初步處理。處理的流程如下:
a. 本地聚合,將本地看到的數據進行trade_date聚合計算
b. 本地排序,將同個分佈桶內的數據根據trade_date進行排序
c. 重分佈排序,是Reduce之前的處理。它將多個前置Worker產生的多個分佈桶數據進行歸併排序
d. 歸約聚合,再將最終的數據進行全局的聚合

如上是一般情況下Trades數據時的操作流程。本地聚合起到了減少數據量的功能,對於Worker_0輸入有3條記錄,但向後輸出只需要2條記錄。本地排序和重分佈排序是將數據排序,使得歸約聚合操作每次只針對一個group進行。
接下來,咱們看一些特殊的數據,如下圖2:

trade.2.png

圖2中可以看到每個trade_date只有一筆交易。從剛纔通用的處理流程中可以看到:
a. 本地聚合沒起到任何數據減少作用,因爲每個Worker收到的交易列表裏沒有重複trade_date
b. 兩個排序操作進行數據歸併處理也是無用功,因爲每筆交易都是屬於一個group
對於此種場景,我們就可以把“本地聚合”、“本地排序”和“重分佈排序”階段去掉,直接跳至歸約聚合即可以。
這些數據特徵可以通過何種方式發現呢?這就是本篇想要引出的主題——Metadata(元數據)

Metadata

什麼是Metadata?概括地說,它是數據特徵的描述。SQL描述了數據的處理邏輯,從原始數據作爲初始數據集合,經過關係代數的基本運算而得到最終的結果數據集合。而Metadata信息的最初始來源是原始數據自身的特徵,同時包含了中間過程的數據推導計算。
藉由前面的例子,將SQL轉換成關係代數的運算符描述,如圖:
md.1.png

TableScanOp0_trades表示從表trades上讀取原始數據
AggOp1表示將原始數據根據trade_date進行聚合操作
SelectOp2表示將聚合的結果展現出來
AdhocSinkOp3表示在標準輸出中顯示結果

前面場景所描述的“本地聚合”、“本地排序”、“重分佈排序”和“歸約聚合”就是AggOp1的物理執行操作描述。

優化器拿到此邏輯操作DAG圖後就開始着手將它轉換成物理操作DAG圖(TableScanOp0_trades、SelectOp2和AdhocSinkOp3在此處不着重講述)。優化器會生成代數等價的兩種選擇,如下圖所示:
兩種執行計劃如何選擇,優化器依靠的是cost(代價)計算。plan的輸入數據量是一致的(假設爲rc0)。

md.2.png md.3.png

對於plan a,它對輸入數據沒有任何處理,所以網絡分發和排序的數據量爲rc0,而後對rc0的數據進行reduce端的聚合操作。

對於plan b,它對輸入數據進行了本地聚合(HashAgg),若本地存在重複Key的數據,則網絡分發和排序的數據量則會壓縮成rc1(假設壓縮後的數據量)。

當HashAgg計算結果的“壓縮”率越高(即rc1越小),則網絡分發和排序的數據量就越小。如Trade1和Trade2兩種數據特徵。Trade1情況下,利用HashAggOp就可以減少網絡分發和排序的數據量。而Trade2因爲數據不存在重複性(交易單中每天只有一單交易),所以Plan b的HashAggOp沒有減少數據量,如此StreamLineWriteOp的輸入數據跟Plan a的StreamLineWriteOp是一樣的。從整體上看,Plan b增加了HashAggOp的計算的浪費,所以Plan a的代價比較小,如此在Trade 2情況下就會選擇Plan a。
從上面的分析可以看出,關鍵點在於HashAggOp操作產生的數據特徵。HashAggOp是進行本地聚合,當輸出減少的數據量所獲得的利潤空間大於HashAggOp自身的計算代價時,Plan b就會被推舉。而如何判斷輸出數據量呢?優化器利用的是一種Metadata:Number of Distinct Value (NDV)。對於Trade例子,HashAggOp操作想要知道它的輸出數據量,就需要知道它的輸入操作符中對於相關列的NDV值,即Output(HashAggOp) = NDV(inputOp)。NDV的計算依賴於三個信息:操作符類型、引用列和當前的過濾條件。inputOp可能是各種各樣的操作符,而不同的操作符,計算的NDV的算法有所不同:

  • Aggregate:根據列的來源(當前操作符生成或是輸入操作符傳遞),將引用列和過濾條件進行分離。根據輸入操作符,以及相關的引用列和過濾條件獲得輸入操作符的NDV,再根據當前過濾條件的選擇率,兩者相乘得到當前操作符的NDV。 NDV(Aggregate, groupKey, predicates) = NDV(inputOp, pushableGroupKey, pushablePredicates) * Selectivity(notPushablePredicates)
  • Filter:將filter自身的過濾條件與後置操作符傳遞而來的過濾條件合併後作爲輸入操作符的過濾條件,計算的結果作爲當前操作符的NDV。 NDV(Filter, groupKey, predicates) = NDV(inputOp, groupKey, union(predicates, conditions)
  • Project:根據列的來源(當前操作符生成或是輸入操作符傳遞),將引用列和過濾條件進行分離。根據輸入操作符,以及相關的引用列和過濾條件獲得輸入操作符的NDV,再根據當前過濾條件生成選擇率和當前操作符引用列的cardinality進行修正NDV。 NDV(Project, groupKey, predicates) = NDV(inputOp, pushableGroupKey, pushablePredicates) * Selectivity(notPushablePredicates) * Cardinality(notPushableGroupKey)
  • Join:根據列的來源將引用列和過濾條件進行分離。計算各個輸入操作符的NDV。再根據Join操作符的可能最大NDV個數與Join操作符行個數計算出實際連接而產生的NDV值。最後再通過當前的過濾條件進行修正。 NDV(Join, groupKey, predicates) = guessNDV(MAX(NDV(inputOp, pushableGroupKey, pushablePredicates)), ROW(Join)) * Selectivity(notPushablePredicates) (ps:計算Join操作符的可能最大NDV個數,可以有多種策略:一種是取inputOp的最大NDV值;另一種是最inputOp的NDV值相乘積。第二種是理論最大值,但它往往與實際的數據情況相差較大,所以一般使用第一種策略。)
  • TableScan:根據引用列查找系統收集的數據NDV,並通過過濾條件進行修正。

目前優化器針對於原始數據NDV計算有兩種方式:一種是Analyze語句手動觸發;另一種是在數據生成時並行收集。這兩種方法收集的統計項除了NDV外還有其它一些常用的信息,包含:
a. avgColLen:平均行的長度
b. maxColLen:最大行的長度
c. minValue:最小值
d. maxValue:最大值
e. estimateCountDistinct:即NDV,不同值個數
f. numNulls:null個數
g. numFalses:false個數(boolean有效)
h. numTrues:true個數(boolean有效)
i. topK:前k個值的佔比
這些統計信息被用來Metadata的原始數據,最終體現在Metadata的演算中。

結語

Metadata是優化器的核心模塊,它爲優化系統提供更多的數據信息以獲得更優的執行計劃。除了MdDistinctRowCount(NDV)外,我們還提供了MdPredicates(獲得前置謂詞)、MdRowCount(獲得數據行數)、MdSize(獲得數據列長度)等等。後續會進一步詳細地介紹其它的Metadata使用。


原文地址


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