YugabyteDB 為 PostgreSQL 帶來了雲端原生的可擴展性和彈性。為了實現這一目標,我們將現有的 PostgreSQL 查詢層與跨多個節點複製和分發資料的分散式儲存層結合。
我們的目標是確保將 PostgreSQL 應用程式移轉到 YugabyteDB 的開發人員能夠享受分散式系統的所有優勢,
而無需犧牲現有的 PostgreSQL 功能或效能。
分散式 SQL 資料庫的一個關鍵挑戰是實現寫入和讀取操作的高吞吐量和一致性。我們開發了多種最佳化來實現高寫入吞吐量,例如,透過寫入緩衝來減少 RAFT 共識延遲。在本部落格中,我們將重點放在基於成本的優化器,這是實現讀取 尼日利亞 電話號碼庫 操作高吞吐量的難題的重要組成部分。
該專案是我們為實現 PostgreSQL 效能平價而做出的更廣泛努力的一部分。在我們最近發布的部落格中了解有關此計劃的更多資訊。
為什麼我們決定為 YugabyteDB 建立一個新的基於成本的最佳化器
SQL 是一種聲明性語言。這意味著使用者描述查詢結果應該如何顯示,而不是如何計算結果。查詢最
佳化器的工作是確定查詢的最佳執行計劃。
優化器必須決定使用哪些索引、使用哪些連接演算法以及使用哪種連接順序以獲得最佳效能。為此,查詢計劃程序模擬不同的執行計劃並估計執行每個計劃的成本。它選擇成本最低的計劃。這稱為基於成本的最佳化 (CBO)。
透過重複使用 PostgreSQL 的查詢層,我們能夠利用查詢最佳化器中的大部分健壯且經過實戰檢驗的程式碼。
但是,我們不能簡單地重複使用 PostgreSQL 中的 CBO,因為 YugabyteDB 的效能特性與 PostgreSQL 不同。例如,由於額外的網路往返,與單一伺服器 PostgreSQL 實例相比,多節點 YugabyteDB 叢集中的巢狀循環成本更高。
在 YugabyteDB 的早期,我們開發了一種基於啟發式的 CBO。雖然簡單,但事實證明它對於大多數客戶用例都是有效的。如果基於啟發式的 CBO 未能找到最佳計劃,我們的用戶可以利用擴充功能
pg_hint_plan來強制優化器選擇更有效的計劃。
然後,我們集中精力開發新技術,以最大限度地提高分散式儲存後端的吞吐量。這包括批量嵌套循環連接、過濾器下推等(稍後會詳細介紹)。然而,這些最佳化很難在啟發式 CBO 中建模,這意味著 CBO 有時會錯過利用最佳化的機會並選擇次優計畫。
我們的客戶也開始部署具有多樣化和複雜拓撲的 YugabyteDB 叢集。叢集拓撲可以極大地影響執行計劃的效能。同樣,事實證明,在啟發式 CBO 上對網路吞吐量和唯讀副本和地理分區等功能進行建模很困難,並且客戶注意到有時沒有選擇最佳計劃。
很明顯,我們需要回到繪圖板,設計和開發一個為 YugabyteDB 量身定制的新 CBO。事實證明,這是一個令人興奮且具有挑戰性的項目,需要各個 YugabyteDB 團隊之間的密切合作。
新的 CBO 幫助我們實現了顯著的 pronovias 使用 pinterest 趨勢徽章為其 2024 年系列發起品牌關注度活動 效能改進(您將在本部落格後面看到)。我們的 CBO 帶來以下好處:
它準確地模擬了我們分散式儲存後端的複雜性,
例如 LSM 索引查找、資料複製和分發、網路吞吐量等。
它對批量嵌套循環連接和過濾器下推等優化進行建模。
它了解叢集拓撲並可以選擇最能利用可用資源的計劃。
它被設計為可擴展以模擬未來的改進。
我們開發了一個名為 TAQO(測試查詢優化器的準確性)的特殊框架,用於 CBO 的驗證測試。本部落格稍後對此進行了詳細介紹。我們使用此工具根據多個基準測試 CBO。結果表明,新成本模型的效率明顯優於啟發式成本模型,與PostgreSQL的效率非常接近。這大大提高了所有工作負載的效能。
PostgreSQL 基於成本的最佳化器如何運作?
如前所述,查詢規劃器的工作是為查詢找到最佳執行計劃。
考慮以下查詢範例。我們希望找到該Science Fiction類型的所有可用書籍,以及每本書的作者和出版商的姓名。
SELECT b.book_title, a.author_name, p.publisher_name
來自作者a
INNER JOIN books b ON a.author_id = b.author_id
INNER JOIN發布者 p ON b.publisher_id = p.publisher_id
WHERE b.genre = ‘科幻小說’ ;
執行此查詢的一種可能計劃如下圖所示,為一棵樹。每個節點代表一個操作,例如掃描或連接。
節點掃描或加入
我們也可以想像其他執行計劃,對基底表有不 台灣數據 同的掃描類型(順序掃描或索引掃描)、不同的連接順序和連接演算法。查詢的可能執行計劃的搜尋空間隨著連接表和這些表上的索引的數量呈指數增長。
PostgreSQL 中的查詢規劃器包含四個關鍵元件,如下圖所示。
執行計劃搜尋演算法
統計數據
選擇性估計
成本模型
PostgreSQL 中的查詢規劃器
為了優化最佳計劃的搜索,PostgreSQL 使用基於動態規劃的演算法(如上圖左側所示)。它不是列舉和評估每個可能的執行計劃的成本,而是分解問題並為查詢的每個部分找到最佳的子計劃。然後將子計劃組合起來以找到最佳的總體計劃。
其他三個組件用於計算每個子計劃的成本。 PostgreSQL 收集統計信息,例如表中每列的不同值的數量和資料分佈的直方圖。這通常作為 VACUUM 的一部分在 PostgreSQL 上自動觸發,但也可以使用 ANALYZE 命令手動觸發。
這些統計資訊用於選擇性估計,其中查詢計劃程序嘗試透過分析篩選器和連接謂詞來預測執行計劃中每個操作將產生的行數。
選擇性估計被饋送到成本模型以預測執行操作以產生結果的成本。
YugabyteDB 需要改變什麼?
PostgreSQL 中的查詢規劃器已經過數十年的微調和實戰測試。幸運的是,我們可以在 YugabyteDB 中重複使用大部分強大的程式碼。搜尋演算法和選擇性估計不需要改變。
我們必須實現一種新的機制來對 DocDB 中的資料進行採樣以收集統計信息,並且我們重用 PostgreSQL 中的程式碼來計算統計資料。注意:目前在 YugabyteDB 上不會自動觸發,必須使用 ANALYZE 指令手動觸發。目前正在進一步優化 ANALYZE 的效能,並根據表上的寫入活動量自動觸發它。
我們還必須開發一個新的成本模型來準確建模 DocDB 儲存引擎並估計執行計劃的成本,下一節將對此進行詳細介紹。
新成本模型如何運作?
為了解釋新成本模型的工作原理,我們詳細介紹如何對 YugabyteDB 的一些關鍵方面進行建模。讓我們考慮索引掃描的情況,因為順序掃描的成本模型相對簡單,並且重複使用了索引掃描成本模型的部分內容。
文件資料庫存儲
YugabyteDB 使用稱為DocDB的分散式儲存層。它是基於日誌結構合併樹( LSM )索引的鍵值儲存。您可以在上面的連結中找到更多詳細信息,但以下是 DocDB 工作原理的簡化說明。
YugabyteDB 中的每個元組都儲存為鍵值對,其中鍵是主索引鍵的編碼形式,值以序列化格式包含表中其他列的內容。對錶的寫入首先緩衝在記憶體表中,記憶體表可以被視為記憶體中排序的資料結構。當記憶體表達到閾值大小後,它就會變得不可變並作為 SST(排序字串樹)檔案刷新到磁碟。
排序字串樹
由於 SST 檔案是不可變的,因此元組中某些列的更新儲存為單獨的鍵值對,如下所示。要取得元組的最新版本,我們必須在所有 SST 檔案中搜尋鍵並將它們合併以建構元組。同樣,刪除也用墓碑標記。
隨著時間的推移,多個 SST 檔案會被壓縮。壓縮和最佳化(例如布隆過濾器)有助於減少需要查找特定鍵的 SST 檔案的數量。
SST抬起頭來
為了模擬這種複雜性,我們收集統計信息,包括每個元組的鍵值對的平均數量和每個表的 SST 檔案數量。我們使用這些來估計從 DocDB 取得元組的成本。
注意:我們目前使用啟發式值來表示每個元組的鍵值對平均數量以及成本模型中每個表的 SST 檔案數量。在ANALYZE期間收集這些統計數據的工作正在進行中。啟發式值在大多數情況下工作得相當好,但可能會在具有太多部分更新或太多 SST 檔案的表中導致問題。
LSM 索引查找
尋找 DocDB 鍵的操作稱為「查找」。取得後續鍵的操作稱為next。取得前一個鍵的操作稱為previous。
一次查找比下一次和上一次查找要昂貴得多。由於資料的壓縮方式,前一個操作比後一個操作稍微昂貴,這導致向後索引掃描與前向掃描相比效率較低。
每個seek、next和previous操作的成本是透過考慮SST檔案的數量和每個元組鍵值對的平均數量來計算的。
為了預測索引掃描期間將執行的查找、下一個和上一個操作的數量,我們對 LSM 索引的工作原理進行建模。
LSM 索引可用於尋找各個按鍵,也可用於掃描鍵之間的一系列值。它也可用於最佳化部分索引鍵的查找。 DISTINCT 操作也可以下推到 DocDB 並使用索引進行最佳化。
透過分析查詢,我們確定索引的使用方式。根據列統計信息,我們嘗試預測將執行的查找、下一個和上一個操作的數量。我們透過將其乘以操作的估計成本來獲得索引查找的總成本。
考慮以下範例:我們有一個包含 100 萬行的表和一個包含 4 列的主索引鍵。