揭祕!Greenplum並行執行引擎到底是如何工作的?

本文轉載自:Greenplum社區

首先我們先來了解一下什麼是執行器。簡單來講,執行器是處理一個由執行計劃節點組成的樹,並返回查詢結果。那麼什麼是執行計劃節點呢?從本質上講,一個執行計劃節點,實際上就是一個數據處理節點。從下圖可看到,在數據輸入後,執行節點會對數據進行數據處理,然後返回數據作爲輸出。這些執行節點會被組織成樹的形式。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

下圖是一個SELECT查詢的執行計劃樹。通過優化器優化後,就會生成這樣的樹狀結構,我們可以看到裏面有四個執行節點,包括HashJoin節點,Hash節點,順序掃描節點,所有的節點通過樹的方式組織在一起,來表示各節點之間的數據流動或者順序關係。 每一個計劃節點包含足夠多的元數據信息提供給執行器。

 

圖中的Seq Scan被稱爲原發性的掃描節點,原發性的掃描節點是指,節點本身可以自己產生數據,而不依賴於其他節點;反之,非原發性掃描節點是需要子節點來爲其提供數據,圖中的Hash Join和Hash就是非原發性掃描節點。瞭解了原發性掃描節點和非原發性掃描節點的不同,就可以更好的理解後面的執行模型。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

那麼執行器是怎麼執行生成的執行計劃樹呢?就需要利用執行模型了。面對這樣的執行計劃樹時,處理方式其實很多,我們會根據包括每一個節點內的數據輸入是怎麼樣的規定,輸出有什麼樣的特點等不同的信息,會選擇不同的執行模型。

現在我們來介紹一下幾種常見的執行模型。

執行模型

第一種是迭代模型,也被稱爲流式模型,或者是抽拉式模型。它的定義非常簡單,每一個執行節點本質上就是一個next函數,我們會從一個樹節點的根節點一直往下執行這個next 函數。next 函數的實現會遵循這樣的特點:

  • 從輸出角度看,next 函數的每一次調用,執行節點返回一個tuple,沒有更多tuple的時候返回一個NULL。
  • 從輸入的角度看,執行節點實現一個循環,每次調用子執行節點的next函數來獲取它們的輸出,並處理它們直到能返回一個tuple或者NULL。
  • 執行控制流方向是自上往下,不斷抽拉的方式,由上層節點直接驅動下層節點來進行數據的驅動。而從數據流的角度來看,還是由上層節點往下層節點傳輸來完成。

這種執行模型的有點在於規則簡單,易懂,資源使用少,通用性好,大部分的執行計劃節點一般都可以用這種模式來實現。缺點也很顯而易見,由於每次迭代只返回一個tuple,迭代次數多,代碼局部性較差,同時對CPU cacheline也不是很友好。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

向量化模型

第二種模型就是向量化模型,和迭代模型有一些相似之處,比如每一個執行節點實現一個next函數,但也有其不同之處。每一次迭代,執行節點返回一組tuple而非一個tuple,從而減少迭代次數,可以利用新的硬件特性如SIMD來加快一組tuple的處理。同時一組tuple在不同的節點之間傳輸,對列存也更加友好。

執行節點實現一個循環,每次調用子執行節點的next函數來獲取它們的輸出,並能夠批量的處理數據。執行控制流方向自上而下,採用pull的方式。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Push執行模型

第三種模型是目前比較熱門的模型——PUSH執行模型。每一個執行節點定義兩個函數

  • Produce函數

Produce函數:看起來像是一個執行節點tuple的生產函數,其實不然,對於非自主生產的執行節點,produce函數更像一個控制函數,它不做過多的生產的工作,想反它會立即調用子節點的produce函數。具有自主生產的執行節點(一般爲葉子節點),其produce函數名副其實的生產tuple,並驅動父節點的consume函數提取數據。

  • Consume函數

Consume函數:被下層節點驅動調用,接收子節點數據,進行各種運算,並驅動其父節點的consume函數。

現在我們通過一個例子來看一下,下圖中有三個節點,一個掃描節點,一個投影節點,一個Join 節點。每個節點都生成了兩個函數,一個生產函數,一個消費函數。整個PUSH模型是怎麼做的呢?圖中的紅框標註的爲原發性的掃描節點,藍框標註的是非原發性的掃描節點。非原發性的掃描節點中的生產函數並不做真正的生產工作,而更多是承擔了控制工作,會調用它的子節點的生產函數。因此投影節點和Join節點會調用scan的生產函數。由於Scan是原發性的,因此會在生產並得到數據後,開始驅動數據的消耗。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

PUSH模型是由下層的節點驅動上層的節點來完成的。數據流向也是自下而上的。下層驅動模型可以相對容易的轉換成由數據驅動的代碼。好處就是,上層的操作就會變成本節點的算子,增加代碼的局部性。此外,這樣的代碼可以更方便進一步轉換爲一個純計算代碼,例如使用LLVM優化等。個人認爲這種模型通用性不強,只能做一些局部的優化。

Greenplum使用的是迭代模型,但我們正在積極探索向量化模型和PUSH模型。Greenplum正在開發相應的功能,並提交到PG社區,基本思路是利用custom scan 的可定製特性,實現向量化版本的AGG節點,SORT節點,並替換原有查詢執行樹中的相應節點。大家對這一塊感興趣也歡迎去相應的郵件列表查看。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

而Greenplum執行器面臨了更大的挑戰,首先Greenplum是MPP架構,意味着大規模的並行計算,每個執行節點就需要更多的處理過程。同一個執行節點就會變成多個處理過程,而數據也會被拆分。執行節點之間進行輸入和輸出的過程中,需要不同的計算單元進行交換。

Greenplum執行的挑戰和解決方案——Motion

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

此外,Greenplum是一個Shared-Nothing的架構,這就意味着不同的計算單元之間的輸入輸出的過程會受阻。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

面臨這樣的挑戰,Greenplum的解決方案是加了一個新的名爲MOTION的執行節點,用來在不同的執行節點之間移動數據。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

加了Motion後,執行計劃仍然是樹狀結構。只是在不同的節點之間加了個Motion節點,並最終通過Motion節點,將數據進行彙總。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

接着我們來剖析一下並行化Plan。在下面的例子中,我們有一個Master和34個Segment節點。現在有兩張表:單身男和單身女,數據分佈在不同的SEGMENT上。如果我們要進行一個查詢,將這兩張表格中,籍貫相同的單身男和單身女進行相親匹配,我們是如何生成一個可以被並行化執行的計劃樹呢?

爲了更好的說明這個問題,我們可以在現實生活中進行映射,來方便大家理解。如果在現實生活中,我們會怎麼辦?如果這些不同戶籍的單身男女在同一個省,此時處理方法就相對簡單,

  • 首先把單身女找出來
  • 再把單身男找出來
  • 再把同戶籍的男生女生分配到相同的會場

從而較爲快速的把這些單身男女進行匹配和篩選。

如果這些單身男女並不在同一個省,而是分佈在全國34個省中,此時要如何處理呢?

爲了做一個最優的策略,我們會分情況來看,

  • 可以由各省獨自舉辦相親會
  • 針對本省的單身男女組織相親
  • 將結果返回總部

對應到Greenplum上,是這樣的

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

2. 對於單身女居住在戶籍所在地,而單身男生分散在全國各地。此時採取的策略可以是,

  • 各省的分部獨自舉辦相親會:
  • 將每個省的單身男青年找出來,並將他們通過火車派送回原戶籍所在地
  • 由每個省接待這些男青年,並在本省找出女單身青年,對他們進行相親配對。

如果女生數量很少,此時可以採用的策略是

  • 找到本省所有適齡單身女青年,併爲其買好34個省份的車票,每個省份都去一趟。
  • 每個省接待這些單身女青年,並安排其與生活在本省的男青年相親,找出戶籍一致的配對。

對應到Greenplum上,是這樣的

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

3. 如果單身男女隨機分佈在全國各地,此時有兩種策略

策略1:在總部舉辦相親會,各省把單身男女通過火車派送回總部,總部接待並安排相親配對。但由於總部資源有限,一般都不會採取這種策略;

策略2:

  • 各分部舉辦相親會:
  • 各省找出居住在本省的適齡單身男,並按戶籍派送到相應的省。
  • 各省找出居住在本省的適齡單身女,並按戶籍派送到相應的省。
  • 各省接待全國歸來的男女,進行相親配對。

對應到Greenplum上,就是這樣的:

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

在進行相親策劃後,我們得出了以下經驗總結:

  • 人多力量大的原則,儘量利有各省的分部
  • 要首先分析當前男女青年的地域分佈
  • 必要時使用交通工具來打破地域的限制

其實在Greenplum裏,也採用了類似的處理方式。每一張表都會有數據分佈信息,Greenplum支持三種分佈策略:鍵值分佈(按列分佈)、隨機分佈、複製分佈(數據在所有的segment上都保留了一份數據)。

Greenplum內部採用更通用的Locus信息來表示分佈信息,所有的數據集合都會有數據分佈狀態的。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Greenplum通過Motion來打破物理上的隔離。包括下圖中的四種Motion。Redistribute Motion是通過鍵值把Tuple在多個節點間進行重分佈。Gather/Gather Merge Motion是把不同Segment上的數據聚集到一個節點上,Gather Merge保證了一個有序的收集過程。Broadcase Motion顧名思義就是廣播,每個節點都發送一份。Explict Redistribute Motion常用於Update/Delete操作,該類操作需要在數據原來所在的節點上進行更新或刪除,保證數據分佈不會出現不一致。gp segment id隱藏列保存了數據所在原來節點信息。

並行化Plan

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Motion會引起數據的遷移,帶來執行代價,所以Greenplum會對需不需要做Motion進行代價評估,評估依據主要是當前數據集合的數據分佈狀態和在當前數據集合上將要執行的操作。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

現在我們通過一個分佈式Join的例子來鞏固一下。下面是一個簡單的inner join。A、B都是按照Hash分佈的鍵值表。也就是數據被分散在各個Segment上,而每個Segment上只有部分數據。要做到A inner join B的完整數據集,就需要把B表全部複製到所有的segment上,和A的部分數據Join。得到的Plan就如下圖所示。前面我們提到,在Join完成後,也會有個數據分佈。本例中,在Join完成後,還是會通過Hash分佈。接着,由於QD會直接和Client進行交互,因此需要把所有的數據Gather到QD上,再由QD發送給Client。而其中的優化過程,會在本《深入淺出Greenplum內核》系列直播後續的課程中細講,請大家關注。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

如果A是一個鍵值表,B是一個複製表。前面的Broadcast就不需要做了,可以直接進行Join。每個並行處理單元處理下圖中的計劃樹,再Gather到QD即可。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

如果A是鍵值表,而B是general的數據分佈。B會在每個segment上都能產生1-10的數據,就能滿足Join的需求。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

如果A不變,而B是一個子查詢,是SingleQE的數據分佈,即在一個segment上提供這樣的數據。其中一種策略就是,把分佈各個Segment上的A的數據都Gather到一個Segment上執行。此時Join後的數據模型就會變成SingleQE的數據分佈。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

如果在Inner Join時加個條件,就可以將Broadcast Motion換成Redistribute Motion。讓c2這一列按照c1這個Hash重新分佈到其他segment上,從而減少數據的移動。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

我們再來看一個要AGG操作的例子,在下面的例子中,對A進行AGG操作,計算c1的count值。此時,我們只需要在每個Segment上做AGG,再Gather到QD即可。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

如果A表是按照C2做分佈的(非兩階段),則前面的策略便不可用了。此時,我們可以將A可以按照C1做Redistrbute Motion,在前面提到的操作即可。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Dispatcher

講完分佈式Plan的產生,我們再來看一下Greenplum中爲了支持分佈式plan而設計的模塊。第一個就是Dispatcher。

上面提到的相親的策略,

  • 各省的分部獨自舉辦相親會。
  • 首先每個省的單身男青年找出來,並將他們通過火車派送回原戶籍所在地。
  • 然後每個省接待這些男青年,並在本省找出女單身青年,對他們進行相親配對。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

對應到Greenplum上,有了分佈式plan,一堆計算資源是如何分配調度和執行起來的呢?

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Dispatcher首先要做的就是分配QE資源。從plan的角度來看,會將計劃做成SliceTable,SliceTable中會告知Slice2從34個segment來分配資源,而Slice3只需要Segment2來提供資源即可。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Dispatcher從SliceTable中得到信息後,會去分配資源。它會向CdbComponentDatabases這個component來申請資源,並將得到的資源回寫到SliceTable中。原本,SliceTable中只包括了需要在哪幾個Segment上起QE資源的較模糊的指令,但在分配完後,每個SliceTable就會得到QE資源具體的節點信息,包括地址和端口等。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Dispatcher分配QE資源通過調用allocateGang()函數完成。GANG大小的分配非常靈活,最小可以只分配一個QE資源,而一般爲segment的個數,甚至可以支持大於segment的個數的QE資源,即每個segment可以爲一個gang分配多於一個的QE資源。此外QE資源閒置後,並不會被馬上回收,而是可以被後續的查詢重用,減少了重複分配QE帶來的開銷。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Dispatcher第二個功能是分發任務。CdbDispatchPlan可以分發並行性化plan的任務,SliceTable也會連同這個分佈式plan一起發給QE。這樣的話所有的QE通過SliceTable可以找到自己預先被分配屬於哪個Gang,以及它的父節點的Gang是哪些以便於建立節點間通信。通過Parent Gang具體的QE描述符,我們就可以知道要把數據傳送到哪個端口。也可以分發純文本的、兩階段提交、查詢樹的任務。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Dispatcher的第三個功能就是協調功能,通過
cdbdisp_checkDispatchResult函數來控制QE的狀態。有下面四種等待模式。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

下圖就是一個典型的Dispatcher程序。Greenplum內的代碼基本都會遵循這樣的邏輯:分配上下文-分配資源-發送任務-等待發送的完成-等待QE的狀態-銷燬上下文。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

Interconnect

第二個模塊就是Interconnect。Greenplum是通過網絡在QE之間移動數據,這個網絡模塊就是Interconnect。在Motion節點被初始化時,發送端和接收端就會建立Interconnect網絡連接。在Motion節點執行時,就會通過Interconnect來發送數據。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

下圖是Interconnect的分層介紹。從應用層來說,主要任務是發送數據。Interconnect會對Tuple進行包裝,將其包裝成一個個Chunk。有些Tuple很大,就會進行切割,將其切成多個Chunk。Chunk通過數據包發送給receiver端。應用層還有一些數據流控制的包,包括EOS包,STOP包等。所有的包都會通過系統傳輸層中的UDPIFC和TCP IC進行傳輸。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

UDPIFC是Greenplum自己實現的一種RUDP(Reliable User Datagram Protocol)協議。基於UDP協議開發的,爲了支持傳輸可靠性,實現了重傳,亂序處理,重傳處理,不匹配處理,流量控制等功能。GPDB當初引入UDPIFC主要爲了解決複雜OLAP查詢在大集羣中使用連接數過多的問題。UDPIFC實際上是一種線程模型。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

後續,我們也可能會增加一些新的Interconnect類型,包括QUIC協議,Proxy協議等,歡迎大家的關注。

揭祕!Greenplum並行執行引擎到底是如何工作的?

 

關於Hashjoin的內容,由於時間原因,本次分享就不做詳細的講解,如果大家對這一塊感興趣,可以反饋給我們社區,我們可以在後面添加專門的講解。大家可以參考一下之前Greenplum中文社區公衆號發佈的關於Hashjoin的文章來了解相關內容。

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