Categories
程式開發

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇)


本文經授權轉載自公眾號 PostgreSQL 中文社區,主要介紹了 Greenplum 集群概述、分佈式數據存儲和分佈式查詢優化。

1. 分佈式執行器

現在有了分佈式數據存儲機制,也生成了分佈式查詢計劃,下一步是如何在集群裡執行分佈式計劃,最終返回結果給用戶。

Greenplum 執行器相關概念

先看一個 SQL 例子及其計劃:

test=# CREATE TABLE students (id int, name text) DISTRIBUTED BY (id);
test=# CREATE TABLE classes(id int, classname text, student_id int) DISTRIBUTED BY (id);
test=# INSERT INTO students VALUES (1, 'steven'), (2, 'changchang'), (3, 'guoguo');
test=# INSERT INTO classes VALUES (1, 'math', 1), (2, 'math', 2), (3, 'physics', 3);

test=# explain SELECT s.name student_name, c.classname
test-# FROM students s, classes c
test-# WHERE s.id=c.student_id;
                                          QUERY PLAN
----------------------------------------------------------------------------------------------
-
 Gather Motion 2:1 (slice2; segments: 2) (cost=2.07..4.21 rows=4 width=14)
   -> Hash Join (cost=2.07..4.21 rows=2 width=14)
        Hash Cond: c.student_id = s.id
        -> Redistribute Motion 2:2 (slice1; segments: 2) (cost=0.00..2.09 rows=2 width=10)
             Hash Key: c.student_id
             -> Seq Scan on classes c (cost=0.00..2.03 rows=2 width=10)
        -> Hash (cost=2.03..2.03 rows=2 width=12)
             -> Seq Scan on students s (cost=0.00..2.03 rows=2 width=12)
Optimizer status: legacy query optimizer

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 1

這個圖展示了上面例子中的 SQL 在 2 個 segment 的 Greenplum 集群中執行時的示意圖。

QD(Query Dispatcher、查詢調度器):Master 節點上負責處理用戶查詢請求的進程稱為 QD(PostgreSQL 中稱之為 Backend 進程)。 QD 收到用戶發來的 SQL 請求後,進行解析、重寫和優化,將優化後的並行計劃分發給每個 segment 上執行,並將最終結果返回給用戶。此外還負責整個SQL 語句涉及到的所有的QE 進程間的通訊控制和協調,譬如某個QE 執行時出現錯誤時,QD 負責收集錯誤詳細信息,並取消所有其他QEs;如果LIMIT n 語句已經滿足,則中止所有QE 的執行等。 QD 的入口是 exec_simple_query()。

QE(Query Executor、查詢執行器):Segment 上負責執行 QD 分發來的查詢任務的進程稱為 QE。 Segment 實例運行的也是一個 PostgreSQL,所以對於 QE 而言,QD 是一個 PostgreSQL 的客戶端,它們之間通過 PostgreSQL 標準的 libpq 協議進行通訊。對於 QD 而言,QE 是負責執行其查詢請求的 PostgreSQL Backend 進程。通常 QE 執行整個查詢的一部分(稱為 Slice)。 QE 的入口是 exec_mpp_query()。

Slice:為了提高查詢執行並行度和效率,Greenplum 把一個完整的分佈式查詢計劃從下到上分成多個 Slice,每個 Slice 負責計劃的一部分。劃分 slice 的邊界為 Motion,每遇到 Motion 則一刀將 Motion 切成發送方和接收方,得到兩顆子樹。每個 slice 由一個 QE 進程處理。上面例子中一共有三個 slice。

Gang:在不同 segments 上執行同一個 slice 的所有 QEs 進程稱為 Gang。上例中有兩組Gang,第一組Gang 負責在2 個segments 上分別對錶classes 順序掃描,並把結果數據重分佈發送給第二組Gang;第二組Gang 在2 個segments 上分別對錶students 順序掃描,與第一組Gang 發送到本segment 的classes 數據進行哈希關聯,並將最終結果發送給Master。

並行執行流程

下圖展示了查詢在 Greenplum 集群中並行執行的流程。該圖假設有 2 個 segments,查詢計劃有兩個 slices,一共有 4 個 QEs,它們之間通過網絡進行通訊。

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 2

QD 和 QE 都是 PostgreSQL backend 進程,其執行邏輯非常相似。對於數據操作(DML)語句(數據定義語句的執行邏輯更簡單),其核心執行邏輯由 ExecutorStart, ExecutorRun, ExecutorEnd 實現。

QD:

  • ExecutorStart 負責執行器的初始化和啟動。 Greenplum 通過 CdbDispatchPlan 把完整的查詢計劃發送給每個 Gang 中的每個 QE 進程。 Greenplum 有兩種發送計劃給QE 的方式:1)異步方式,使用libpq 的異步API 以非阻塞方式發送查詢計劃給QE;2)同步多線程方式:使用libpq 的同步API,使用多個線程同時發送查詢計劃給QE。 GUC gp_connections_per_thread 控制使用線程數量,缺省值為 0,表示採用異步方式。 Greenplum 從 6.0 開始去掉了異步方式。
  • ExecutorRun 啟動執行器,執行查詢樹中每個算子的代碼,並以火山模型(volcano)風格返回結果元組給客戶端。在 QD 上,ExecutorRun 調用 ExecutePlan 處理查詢樹,該查詢樹的最下端的節點是一個 Motion 算子。其對應的函數為 ExecMotion,該函數等待來自於各個 QE 的結果。 QD 獲得來自於 QE 的元組後,執行某些必要操作(譬如排序)然後返回給最終用戶。
  • ExecutorEnd 負責執行器的清理工作,包括檢查結果,關閉 interconnect 連接等。

QE 上的 ExecutorStart/ExecutorRun/ExecutorEnd 函數和單節點的 PostgreSQL 代碼邏輯類似。主要的區別在 QE 執行的是 Greenplum 分佈式計劃中的一個 slice,因而其查詢樹的根節點一定是個 Motion 節點。其對應的執行函數為 ExecMotion,該算子從查詢樹下部獲得元組,並根據 Motion 的類型發送給不同的接收方。低一級的 Gang 的 QE 把 Motion 節點的結果元組發送給上一級 Gang 的 QE,最頂層 Gang 的 QE 的 Motion 會把結果元組發送給 QD。 Motion 的 Flow 類型確定了數據傳輸的方式,有兩種:廣播和重分佈。廣播方式將數據發送給上一級 Gang 的每一個 QE;重分佈方式將數據根據重分佈鍵計算其對應的 QE 處理節點,並發送給該 QE。

QD 和 QE 之間有兩種類型的網絡連接:

  • libpq:QD 通過 libpq 與各個 QE 間傳輸控制信息,包括發送查詢計劃、收集錯誤信息、處理取消操作等。 libpq 是 PostgreSQL 的標準協議,Greenplum 對該協議進行了增強,譬如新增了‘M’消息類型(QD 使用該消息發送查詢計劃給 QE)。 libpq 是基於 TCP 的。
  • interconnect:QD 和 QE、QE 和 QE 之間的表元組數據傳輸通過 interconnect 實現。 Greenplum 有兩種 interconnect 實現方式,一種基於 TCP,一種基於 UDP。缺省方式為 UDP interconnect 連接方式。

Direct Dispatch 優化

有一類特殊的 SQL,執行時只需要單個 segment 執行即可。譬如主鍵查詢:SELECT * FROM tbl WHERE id = 1;

為了提高資源利用率和效率,Greenplum 對這類SQL 進行了專門的優化,稱為Direct Dispatch 優化:生成查詢計劃階段,優化器根據分佈鍵和WHERE 子句的條件,判斷查詢計劃是否為Direct Dispatch 類型查詢;在執行階段,如果計劃是Direct Dispatch,QD 則只會把計劃發送給需要執行該計劃的單個segment 執行,而不是發送給所有的segments 執行。

2. 分佈式事務

Greenplum 使用兩階段提交(2PC)協議實現分佈式事務。 2PC 是數據庫經典算法,此處不再贅述。本節概要介紹兩個 Greenplum 分佈式事務的實現細節:

  • 分佈式事務快照:實現 master 和不同 segment 間一致性
  • 共享本地快照:實現 segment 內不同 QEs 間的一致性

分佈式快照

在分佈式環境下,SQL 在不同節點上的執行順序可能不同。譬如下面例子中 segment1 首先執行 SQL1,然後執行 SQL2,所以新插入的數據對 SQL1 不可見;而 segment2 上先執行 SQL2 後執行 SQL1,因而 SQL1 可以看到新插入的數據。這就造成了數據的不一致。

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 3

Greenplum 使用分佈式快照和本地映射實現跨節點的數據一致性。 Greenplum QD 進程承擔分佈式事務管理器的角色,在QD 開始一個新的事務(StartTransaction)時,它會創建一個新的分佈式事務id、設置時間戳及相應的狀態信息;在獲取快照(GetSnapshotData)時,QD 創建分佈式快照並保存在當前快照中。和單節點的快照類似,分佈式快照記錄了 xmin/xmax/xip 等信息,結構體如下所示:

typedef struct DistributedSnapshot
{
       DistributedTransactionTimeStamp     distribTransactionTimeStamp;
       DistributedTransactionId xminAllDistributedSnapshots;
       DistributedSnapshotId distribSnapshotId;

       DistributedTransactionId xmin;     /* XID = xmax 则不可见 */
       int32          count;        /* inProgressXidArray 数组中分布式事务的个数 */
       int32          maxCount;

       /* 正在执行的分布式事务数组 */
       DistributedTransactionId       *inProgressXidArray;
} DistributedSnapshot;

執行查詢時,QD 將分佈式事務和快照等信息序列化,通過 libpq 協議發送給 QE。 QE 反序列化後,獲得 QD 的分佈式事務和快照信息。這些信息被用於確定元組的可見性(HeapTupleSatisfiesMVCC)。所有參與查詢的 QEs 都使用 QD 發送的同一份分佈式事務和快照信息判斷元組的可見性,因而保證了整個集群數據的一致性,避免前面例子中出現的不一致現象。

在 QE 上判斷一個元組對某個快照的可見性流程如下:

  • 如果創建元組的事務:xid (即元組頭中的 xmin 字段)還沒有提交,則不需要使用分佈式事務和快照信息;
  • 否則判斷創建元組的事務 xid 對快照是否可見
  • 首先根據分佈式快照信息判斷。根據創建元組的 xid 從分佈式事務提交日誌中找到其對應的分佈式事務:distribXid,然後判斷 distribXid 對分佈式快照是否可見:
    • 如果 distribXid xmin,則元組可見
    • 如果 distribXid > distribSnapshot->xmax,則元組不可見
    • 如果 distribSnapshot->inProgressXidArray 包含 distribXid,則元組不可見
    • 否則元組可見
  • 如果不能根據分佈式快照判斷可見性,或者不需要根據分佈式快照判斷可見性,則使用本地快照信息判斷,這個邏輯和 PostgreSQL 的判斷可見性邏輯一樣。

和 PostgreSQL 的提交日誌 clog 類似,Greenplum 需要保存全局事務的提交日誌,以判斷某個事務是否已經提交。這些信息保存在共享內存中並持久化存儲在 distributedlog 目錄下。

為了提高判斷本地 xid 可見性的效率,避免每次訪問全局事務提交日誌,Greenplum 引入了本地事務-分佈式事務提交緩存,如下圖所示。每個 QE 都維護了這樣一個緩存,通過該緩存,可以快速查到本地 xid 對應的全局事務 distribXid 信息,進而根據全局快照判斷可見性,避免頻繁訪問共享內存或磁盤。

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 4

共享本地快照(Shared Local Snapshot)

Greenplum 中一個 SQL 查詢計劃可能含有多個 slices,每個 Slice 對應一個 QE 進程。任一 segment 上,同一會話(處理同一個用戶 SQL)的不同 QE 必須有相同的可見性。然而每個 QE 進程都是獨立的 PostgreSQL backend 進程,它們之間互相不知道對方的存在,因而其事務和快照信息都是不一樣的。如下圖所示。

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 5

為了保證跨 slice 可見性的一致性,Greenplum 引入了“共享本地快照(Shared Local Snapshot)”的概念。每個 segment 上的執行同一個 SQL 的不同 QEs 通過共享內存數據結構 SharedSnapshotSlot 共享會話和事務信息。這些進程稱為 SegMate 進程組。

Greenplum 把 SegMate 進程組中的 QE 分為 QE writer 和 QE reader。 QE writer 有且只有一個,QE reader 可以沒有或者多個。 QE writer 可以修改數據庫狀態;QE reader 不能修改數據庫的狀態,且需要使用和 QE writer 一樣的快照信息以保持與 QE writer 一致的可見性。如下圖所示。

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 6

“共享”意味著該快照在 QE writer 和 readers 間共享,“本地”意味著這個快照是 segment 的本地快照,同一用戶會話在不同的 segment 上可以有不同的快照。 segment 的共享內存中有一個區域存儲共享快照,該區域被分成很多槽(slots)。一個 SegMate 進程組對應一個槽,通過唯一的會話 id 標誌。一個 segment 可能有多個 SegMate 進程組,每個進程組對應一個用戶的會話,如下圖所示。

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (下篇) 7

QE Writer 創建本地事務後,在共享內存中獲得一個SharedLocalSnapshot 槽,並它自己的本地事務和快照信息拷貝到共享內存槽中,SegMate 進程組中的其他QE Reader 從該共享內存中獲得事務和快照信息。 Reader QEs 會等待 Writer QE 直到 Writer 設置好共享本地快照信息。

只有 QE writer 參與全局事務,也只有該 QE 需要處理 commit/abort 等事務命令。

3. 數據洗牌(Shuffle)

相鄰 Gang 之間的數據傳輸稱為數據洗牌(Data Shuffling)。數據洗牌和 Slice 的層次相吻合,從下到上一層一層通過網絡進行數據傳輸,不能跨層傳輸數據。根據 Motion 類型的不同有不同的實現方式,譬如廣播和重分佈。

Greenplum 實現數據洗牌的技術稱為 interconnect,它為 QEs 提供高速並行的數據傳輸服務,不需要磁盤 IO 操作,是 Greenplum 實現高性能查詢執行的重要技術之一。 interconnect 只用來傳輸數據(表單的元組),調度、控制和錯誤處理等信息通過 QD 和 QE 之間的 libpq 連接傳輸。

Interconnect 有 TCP 和 UDP 兩種實現方式,TCP interconnect 在大規模集群中會佔用大量端口資源,因而擴展性較低。 Greenplum 默認使用 UDP 方式。 UDP interconnect 支持流量控制、網絡包重發和確認等特性。

4. 分佈式集群管理

分佈式集群包含多個物理節點,少則四五台,多則數百台。管理如此多機器的複雜度遠遠大於單個 PostgreSQL 數據庫。為了簡化數據庫集群的管理, Greenplum 提供了大量的工具。下面列出一些常用的工具,關於更多工具的信息可以參考 Greenplum 數據庫管理員官方文檔。

  • gpactivatestandby:激活 standby master,使之成為 Greenplum 數據庫集群的主 master。
  • gpaddmirrors:為 Greenplum 集群添加鏡像節點,以提高高可用性
  • gpcheckcat:檢查 Greenplum 數據庫的系統表,用以輔助故障分析。
  • gpcheckperf:檢查 Greenplum 集群的系統性能,包括磁盤、網絡和內存的性能。
  • gpconfig:為 Greenplum 集群中的所有節點進行參數配置。
  • gpdeletesystem:刪除整個 Greenplum 集群
  • gpexpand:添加新機器到 Greenplum 集群中,用以擴容。
  • gpfdist:Greenplum 的文件分發服務器,是 Greenplum 數據加載和卸載的最主要工具。 gpfdist 充分利用並行處理,性能非常高。
  • gpload:封裝 gpfdist 和外部表等信息,通過配置 YAML 文件,可以方便的加載數據到 Greenplum 數據庫中。支持 INSERT、UPDATE 和 MERGE 三種模式。
  • gpinitstandby:為 Greenplum 集群初始化 standby master
  • gpinitsystem:初始化 Greenplum 集群
  • gppkg:Greenplum 提供的軟件包管理工具,可以方便的在所有節點上安裝 Greenplum 軟件包,譬如 PostGIS、PLR 等。
  • gprecoverseg:恢復出現故障的主 segment 節點或者鏡像 segment 節點
  • gpssh/gpscp:標準的 ssh/scp 只能針對一個目標機器進行遠程命令執行和文件拷貝操作。 gpssh 可以同時在一組機器上執行同一個命令;gpscp 同時拷貝一個文件或者目錄到多個目標機器上。很多 Greenplum 命令行工具都使用這兩個工具實現集群並行命令執行。
  • gpstart:啟動一個 Greenplum 集群。
  • gpstop:停止一個 Greenplum 集群
  • gpstate:顯示 Greenplum 集群的狀態
  • gpcopy:將一個 Greenplum 數據庫的數據遷移到另一個 Greenplum 數據庫中。
  • gp_dump/gp_restore:Greenplum 數據備份恢復工具。從 Greenplum 5.x 開始,推薦使用新的備份恢復工具: gpbackup/gprestore。
  • packcore:packcore 可以將一個 core dump 文件及其所有的依賴打成一個包,可以在其他機器上進行調試。非常有用的一個調試用具。
  • explain.pl:把 EXPLAIN 的文本結果轉換成圖片。本節中用的計劃樹圖片都是使用這個工俱生成的。

5. 動手實踐

上面概要介紹了把單個 PostgreSQL 數據庫變成分佈式數據庫涉及的 6 個方面的工作。若對更多細節感興趣,最有效的方式是動手改改代碼實現某些新特性。下面幾個項目可以作為參考:

  • 數據存儲:實現 partial table,使得一張表或者一個數據庫僅僅使用集群的一個子集。譬如集群有 200 個節點,可以創建只是用 10 個節點的表或者數據庫。
  • 資源管理:目前的 Gang 只能在一個會話內部共享,實現 Gang 的跨會話共享,或者 Gang 共享池。
  • 調度:目前 dispatcher 將整個 plan 發送給每個 QE,可以發送單個 slice 給負責執行該 slice 的 QE
  • 性能優化:分析 Greenplum 分佈式執行的性能瓶頸,並進行進一步的優化,特別是 OLTP 型查詢的性能優化,以實現更高 tps。
  • 執行器優化:目前 Greenplum 使用 zstd 壓縮 AO 數據和臨時數據,zstd 造成的一個問題是內存消耗較大,如何優化操作大量壓縮文件時的內存消耗是一個很有挑戰的課題。有關更多細節可以參考這個討論(最後部分有簡單的問題重現方法)

對這些項目有興趣者可以聯繫 yyao AT pivotal DOT io 提供更多諮詢或幫助。實現以上任何一個功能者,可以走快速通道加入 Greenplum 內核開發團隊,共應挑戰共享喜悅 🙂

6. 後記

想了一會,本想寫一點打雞血的話吸引更多人加入數據庫內核開發行列,然覺不合自性,作罷。

Greenplum 酒文化比較濃厚,還是分享一個與酒有關的小故事收尾吧。

13/14 年左右有幸和數據庫老前輩 Dan Holle(Teradata CTO,第七號員工)有諸多交集。老爺子談吐優雅而不失幽默,從事 MPP 數據庫已有 30 餘年。每次喝酒至少放兩瓶酒於面前,且同時喝兩瓶酒,笑談此為並行處理;若某一瓶喝的多了,必拿起另一瓶再喝一點,以確保兩瓶餘量保持一致,笑談此為避免傾斜。經常左瓶喝的多了點,拿起右瓶補一口,補多了,再拿起左瓶補一口。如此左右互補,無需山東人勸酒,自己很快進入狀態。

老先生對 MPP 數據庫之愛已融入生活中,令人敬佩。而正是許多這樣數十年如一日的匠人成就了當今數據庫領域的輝煌。期待更多人加入,幕天席地把酒言歡!

關於作者

姚延棟, Greenplum 研發總監,Greenplum 中文社區發起人。致力於 Greenplum/PostgreSQL 開源數據庫產品、社區和生態的發展。

作者介紹

姚延棟,山東大學本科,中科院軟件所研究生。 PostgreSQL 中文社區委員,致力於 Greenplum/PostgreSQL 開源數據庫產品、社區和生態的發展。

相關閱讀

Greenplum:基於 PostgreSQL 的分佈式數據庫內核揭秘 (上篇)