往期回顧: 深入解析ZNBase分布式SQL引擎架構的五大服務組件
導讀
前文提到,ZNBase 是由浪潮開源的一款 NewSQL 分布式數據庫,基于谷歌 Spanner F1 的論文設計,完美繼承了 Spanner 的設計理念,實現了基于對等架構的分布式 SQL 引擎。ZNBase 的 SQL 引擎包含連接、編譯、緩存、分布式日志和分布式執行五大服務組件,實現了多集群多節點協同的高效計算,大大提升了用戶的查詢效率。
為了進一步提升 SQL 引擎的性能,ZNBase 研發團隊結合實際業務需求,在原有架構的基礎上,針對 SQL 引擎的編譯服務、執行服務、算法等方面進行了一系列深度定制化的優化改進工作。本文将這些改進工作逐一展開介紹。
ZNBase 針對 SQL 引擎的優化改進
1.編譯服務優化
1.1 類型、功能、語法兼容
随着日益增多的場景需要,ZNBase 陸續完善了對 PostgreSQL 、Oracle、MySQL 語法、類型、函數的兼容。
1.2 計劃優化
1.2.1 直方圖
ZNBase 還擴展了統計信息功能,除了:表的行數,表中列的 Distinct 值(某一列的唯一值總共有多少條),還額外引入了直方圖。為 CBO 的優化提供了更多的一句。
統計信息獲取的簡單流程如下:
對每個 range 進行抽樣,用蓄水池算法生成樣本集合,然後用樣本進行各種統計信息的預估,将結果通過寫入函數 writeResults,寫進系統表 system.table_statistics 中。
1.2.2 執行計劃管理
ZNBase 擴展了對執行計劃的管理,包括執行計劃綁定、自動捕獲綁定、自動更新綁定等。執行計劃綁定功能使得可以在不修改 SQL 語句的情況下選擇指定的執行計劃。用戶通過綁定執行計劃,可以将計劃存入 ZNBase 中,下次再執行解析後計劃相同的 SQL 語句時,隻要取出之前存入的計劃即可,省去了構建計劃的時間。ZNBase 還會智能地自動捕獲執行頻率較多的并且用戶之前沒有手動為其創建綁定的 SQL 語句,在後台自動為其創建計劃綁定。
由于表數據的變化,如:數據變化、數據結構變化、統計信息變化,可能會導緻之前綁定的執行計劃執行效率降低,ZNBase 将自動檢測執行時間,将綁定好的執行計劃進行優化,為用戶提高複合當前數據場景的更高的執行效率。
2. 執行優化
2.1 矢量算子
ZNBase 還引入了矢量算子,相比基于 Goetz Graefe 論文的“火山”模型,“矢量”模型在計算行數明顯大于列數的場景下,性能會有極大的提升。
從原理上講,這是用一系列專門針對數據類型和計算的特定編譯循環代替了通用的類似于解釋器的 SQL 表達式評估器,因此計算機可以連續執行許多更簡單的任務,大大節省了重複的計算所需要的時間。配合 ZNBase 團隊開發的列式存儲,查詢性能還将有進一步的提升。
目前矢量算子支持的類型有:Array、BIT、BOOL、BYTES、COLLATE、DATE、DECIMAL、INET、INT、INTERVAL、JSONB、SERIAL、TIME、TIMESTAMP、TIMESTAMPTZ、UUID、FLOAT、STRING 等。
目前 ZNBase 支持的矢量算子有:Noop、TableReader、Distinct、Ordinality、Hashjoiner、MergeJoiner、Sorter、Windower 等。
舉例來說,請考慮一個包含三列的 People 表:Id,Name 和 Age。在火山模型中,每個數據行由每個算子處理一次 —— 一種逐行執行方法。相比之下,在矢量化執行引擎中,我們一次傳遞了有限大小的面向列數據的批處理。我們使用一組列,而不是使用元組數組的數據結構,其中每一列都是特定數據類型的數組。在該示例中,分批處理将由一個Id的整數數組,一個 Name 的字節數組和 Age 的整數數組組成。下圖顯示了兩個模型中數據布局之間的區别:
火山模型
矢量模型
SelectName,(Age-30)*50ASBonusFROMPeopleWHEREAge>30;
這樣的語句查詢,在火山模型中,頂級用戶向 Project 算子請求一行,該請求被傳播到底層的 Scan 算子。掃描從鍵值存儲中讀取一行,并将其傳遞給 Select,Select 将檢查該行是否通過了 Age> 30 的謂詞。如果該行通過了檢查,則将其返回給 Project 算子以計算 Bonus = (Age - 30) * 50 作為最終輸出。
火山模型流程圖
一次處理一行,對于每一行,我們都在調用一個完全通用的标量表達式的過濾器!表達式可以是任何東西:乘法,除法,相等檢查或内置函數,甚至可以是一長串這樣的東西。由于這種通用性,計算機在每一行上都有很多工作要做——必須在甚至無法執行任何工作之前檢查表達式的含義。與編譯後的語言相比,這種計算方式與解釋型語言同樣麻煩。
在矢量化模型中,我們采用不同的方式。每個矢量化算子背後的原理是在執行期間不允許任何自由度或運行時選擇。這意味着對于任務,數據類型和屬性的任意組合,應該由一個專門的算子來負責這項工作。對于示例查詢,用戶從算子鍊中請求一批。每個算子都向其子級請求一批,執行其特定任務,然後将一批返回給其父級。
矢量模型流程圖
為了對此進行可視化,請考慮由 SelectIntGreaterThanInt 處理的 People Batch。該算子将選擇所有大于 30 的 Age 值。這個新的 sel_age batch 然後傳遞到 ProjectSubIntInt 算子,該算子執行簡單的減法運算以生成 tmp batch。最後,将這個 tmp batch 傳遞給 ProjectMultIntInt 算子,該算子将計算最終 Bonus =(Age-30)* 50 值。
矢量模型流程圖
2.2 并行優化
在ZNBase的開發過程中也對算子進行了優化,提高了運算效率。
2.2.1 tablereader 并行
Tablereader 通過拆分 Span 進行并行的 baRequest 下發讀取數據,返回的數據封裝進 baResponse 裡面,放入管道由 tablereader 進行并行處理。
tablereader的并行分為以下幾步:
Step1:Span 拆分,邏輯計劃完成後會生成一個 Span ALL(索引、主鍵查詢除外),Span ALL 會根據 table 的 range 邊界拆分從成多個範圍更小的 Span,每個 Span 會獲得相應的 range 信息,根據 rangeID 可以取得對應 range 的副本信息,再根據副本選擇策略(就近選擇、随機選擇、lease holder(默認)),獲取到對應副本的 nodeID,再将該 Span 放入一個 Map 結構(Map[nodeID][]Span)中;
當 tablereader 下發到了對應節點後,再将 Spans 進行均勻分配進 tablereader 的各個 worker fethcer 當中進行并行的數據讀取:
Step2:BatchRequest 下發,對應節點的 tablereader 的每個 fetcher 的 spans 的每一個 span 會封裝為一個 ScanRequest 請求,多個 ScanRequest 請求封裝進一個 BatchRequest(BacthRequest 請求中 header 信息可以指定一次請求返回的最大 kv 數目),該 BacthRequest 經過分發層邏輯後下發至對應節點的對應 Store 進行數據查詢,返回的數據封裝為 BatchResponse,包含多個對應的 ScanResponse,将 ScanResponse 的 kv 數據放入 channel 中,再由每個 fetcher 綁定的 worker 進行 kv 解碼以及後續的處理:
Step3:數據返回,每個 fetcher 的 worker 協程處理(經過 filter 或 render )完每行 kv 數據後都會放入一個 buffer 當中(默認 buffer 緩存<= 64 行),每個 worker 每完成一個 buffer 會将該 buffer 放入 tablereader 的結果管道中,提供 NextRow 和 NextChunk 兩類接口供上層算子調用:
2.2.2 hashjoin 優化
原有 hashjoin 流程圖如下:
原有執行流程存在如下問題:
經過優化後 hashjoin 由 3 個部分完成:
優化前後對比分析:
執行完 hashjoin:
優化前耗時≈t1 m*t2 m*t3
優化後耗時≈t1 (m/n)*t3
Δt≈(m(n-1))/n t3 m*t2
預期:随着 outer 表數據增多和 join worker 協程數增加,理論上優化越明顯。
經優化後有如下優勢:
展望未來
大數據行業的發展促進了國産分布式數據庫的演進,為适應這種發展大潮,雲溪 NewSQL 數據庫 ZNBase 也會促使分布式 SQL 引擎更趨向完善,未來會在語法上完全兼容 Oracle 等傳統數據庫,計劃上加入更豐富的 HBO,完善 Cascade 框架,更加智能的提高用戶的使用體驗,執行上會兼容 HTAP 計算方式,順應當下飛速增長的數據量,适配更多的大數據、人工智能、機器學習場景,提供一個更智能、更高效的 SQL 計算引擎。
參考文檔 [1]. Volcano-An Extensible and Parallel Query Evaluation System
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!