效能提升利器|PolarDB-X超詳細列存查詢技術解讀

阿里妹導讀
本文將深入探討 PolarDB-X 列存查詢引擎的分層快取解決方案,以及其在最佳化 ORC 列存查詢效能中的關鍵作用。
一、引言
在當今資料迅速增長的時代,高效查詢海量資料已成為企業和技術人員面臨的重要挑戰。列式儲存格式,如 ORC,雖然在特定場景下具有明顯優勢,但面對大規模資料集時,查詢速度仍存在瓶頸。本文將深入探討 PolarDB-X 列存查詢引擎的分層快取解決方案,以及其在最佳化 ORC 列存查詢效能中的關鍵作用。我們將分析其設計原理、實現細節和適用場景,展示該方案在大資料查詢中的廣泛應用及其帶來的高效性和可靠性。此外,文章還將介紹 ORC 檔案的儲存結構、資料壓縮與解壓技術,以及執行器中間結果快取的反壓管理策略,說明如何透過分級快取和反壓機制進一步最佳化查詢效能。透過這些內容,讀者將全面瞭解 PolarDB-X 分層快取解決方案在提升列式儲存查詢效率方面的實際效果和技術優勢。
二、多級快取管理
2.1 概述
在 PolarDB-X 的列存查詢引擎中,多級快取管理是提升查詢效能的核心策略。透過在不同層次設定快取,系統能夠根據資料訪問的頻率和特性,實現高效的資料載入和查詢響應。這種分層的快取架構不僅優化了資料讀取路徑,還有效減少了對底層儲存系統的依賴,從而顯著提升了查詢速度。
2.2 ORC 的儲存層次結構
ORC(Optimized Row Columnar)格式是一種列式儲存格式,廣泛應用於大資料處理。其內部結構設計精妙,透過多層次的組織方式,實現了高效的資料壓縮和快速的列級別查詢。

2.2.1 Stripe、Column 和 RowGroup

  • Stripe:ORC 檔案內部預設每 64MB 形成一個 Stripe。Stripe 內部儲存多個 Column(列)。
  • Column:在 Stripe 內部,每個列在一段連續的檔案區域記憶體儲,包含多個 RowGroup。
  • RowGroup:在 Column 內部,每 10,000 行資料劃分為一個 RowGroup(行組)。RowGroup 是 ORC 進行壓縮、解壓縮、編碼、解碼的基本單位。當 Column 內部儲存的總行數不是 10,000 的整數倍時,最後一個 RowGroup 可能不足 10,000 行。
此外,Stripe Footer 和 Index Data 這兩類結構用於儲存位置資訊,方便快速定位資料塊。

2.2.2 SMA 索引

ORC 檔案中,SMA(Statistics Minimum and Maximum)索引用於儲存每個 Stripe 和 RowGroup 的最小值和最大值統計資訊。這些資訊在查詢時可用於快速過濾不相關的資料塊,從而減少不必要的資料讀取,提高查詢效率。
2.3 定位 ORC 資料塊
為了在 ORC 檔案中精確定位到一個數據塊(如 1000 行的資料單元),需要一個唯一的邏輯地址,包含以下 5 個欄位:
  1. StripeId:檔案內部 Stripe 的編號。
  2. ColumnId:Stripe 內部列的編號。
  3. RowGroupId:列內部 RowGroup 的編號。
  4. StartPosition:RowGroup 內部 Block 的起始位置。
  5. PositionCount:Block 內的資料行數。
透過這些欄位,系統能夠準確地定位到 ORC 檔案中的具體資料位置,從而實現高效的資料讀取。
2.4 列存查詢與 ORC 的關係
在列存查詢過程中,執行器(Executor)是資料查詢操作的核心元件。它根據 SQL 語句、運算元(如掃描、聯接等)和謂詞條件,制定執行計劃樹,並執行相應的查詢操作。

2.4.1 執行計劃與 Scan 運算元

執行器生成的執行計劃樹中包含 Scan 運算元,該運算元定義瞭如何對 ORC 檔案執行掃描操作,即讀取特定列的資料。這些操作通常以列為中心,在 ORC 格式的檔案中進行,大大提升了查詢效率。

2.4.2 謂詞條件與 SMA 索引

在執行查詢操作時,Scan 運算元帶有謂詞條件用於過濾資料。ORC 格式的 SMA 索引使得執行器可以在不讀取所有資料的情況下,高效確定哪些 Stripe 或 RowGroup 包含符合條件的資料。透過謂詞條件與 SMA 索引的結合,系統能夠減少資料載入和掃描的工作量,進一步提升查詢效能。

2.4.3 資料壓縮與解壓

ORC 檔案通常使用多種壓縮技術,如 LZ4 通用壓縮和 RunLength 數值壓縮,以減少儲存空間並提升 I/O 效率。執行器在讀取資料時會自動進行解壓縮操作,壓縮比通常在 2 到 4 倍之間,這不僅節省了儲存空間,還提高了資料讀取的效率。
三、整體層次結構
PolarDB-X 列存查詢引擎採用了多級快取機制,包括一級、二級和三級快取,以及底層的 OSS 儲存底座。每一級快取都有其獨特的設計和作用,透過分級查詢和層層快取,系統能夠高效地訪問和管理海量資料。
3.1 流程示意
整體流程可以概括為以下幾個步驟:
  1. 分級快取:系統配置了 三級快取,每級快取如果命中,則返回命中的快取;否則繼續查詢下一級快取,直到 OSS 儲存底座。
  2. 快取空間:一級快取空間不超過執行時記憶體的 20%,二級快取不超過 30%,三級快取儲存在磁碟中,空間約 500GB 到 1TB,最底層的 OSS 儲存空間則幾乎沒有限制。
  3. 訪問速度:一級快取的訪問速度等同於主存訪問延遲;二級快取的訪問速度包含主存訪問延遲和解壓時間;三級快取的訪問速度包含磁碟訪問延遲和解壓時間;OSS 儲存則包含網路訪問延遲和解壓時間。
  4. 壓縮率:一級快取儲存的是解壓後的資料,二級、三級快取以及 OSS 儲存則儲存壓縮資料,壓縮比在 2 到 4 倍之間。
  5. 區域性性原理:越頻繁訪問的資料,儲存得越靠近執行器,以提高訪問效率。
3.2 定址方式
系統透過謂詞條件和索引搜尋,獲取 {StripeId, ColumnId, RowGroupId} 地址,並透過 ORC 檔案的 meta 和 footer 資訊獲取檔案的具體物理位置 {filePath, offset, len},從而在各級快取中進行查詢和資料讀取。
3.3 一級快取的設計
一級快取的設計旨在快速響應高頻訪問的資料請求。當查詢到達時,基於謂詞條件轉換出一級快取的 key,即 {StripeId, ColumnId, RowGroupId},並在一級快取中查詢對應的 Block 列表。如果命中,則直接使用這些 Block 進行計算。由於這些 Block 是從 RowGroup 中順序解壓出的,每個 RowGroup 包含 10,000 行資料,一個 Block 是其中的 1/10,即 1000 行。
為了控制一級快取的空間佔用,當快取空間超過預設閾值(通常是執行時記憶體的 20%)時,觸發 LFU(Least Frequently Used,最少使用頻率)快取淘汰機制,淘汰最少訪問的快取元素。
3.4 二級快取的設計
當一級快取未命中時,系統會透過 {StripeId, ColumnId, RowGroupId} 獲取 {filePath, offset, len} 地址,作為二級快取的 key。在二級快取中搜索對應的壓縮資料位元組陣列。如果命中,則需要對資料進行解壓操作,並將解壓後的資料寫入一級快取。同樣,當二級快取空間佔用達到閾值時,採用 LFU 機制淘汰最少使用的資料。
3.5 三級快取的設計
三級快取位於磁碟層,主要儲存從 OSS 儲存底座獲取的壓縮檔案片段。三級快取以檔案形式儲存之前在二級快取中的位元組陣列,當讀取到相鄰片段時,會進行合併操作,並非同步執行舊檔案的刪除。由於檔案的寫入操作比較耗時,這些操作由非同步執行緒完成。
3.6 OSS 儲存底座
當所有級別的快取都未命中時,系統會透過 {StripeId, ColumnId, RowGroupId} 對映到具體的檔案路徑,從 OSS 儲存中獲取完整的 ORC 檔案,並下載需要的檔案片段進行解壓和讀取。儘管 OSS 的訪問存在網路延遲,但其近乎無限的儲存空間為系統提供了強大的後備支援。
四、第一和第二級快取的設計原理
4.1 列存資料層次結構
PolarDB-X 的列存儲存格式主要由 ORC 檔案構成。從 ORC 檔案到執行器計算層所需的資料結構,可以劃分為三個層次:
  1. 物理儲存結構:純位元組序列構成的檔案結構,由檔案讀介面 InputStream 封裝。讀取過程對於檔案各個位元組區域的含義是無感知的。
  2. 邏輯儲存結構:關注 File、Stripe、Column、Stream、RowGroup、RL(輕量級)壓縮和通用壓縮七個層次結構。
  3. 計算結構:透過檔案、列、Stripe、RowGroup 四級資訊,得到具體型別的 Block,供執行器計算使用。
4.2 物理儲存結構
物理儲存結構是 ORC 檔案的最底層表示,純粹由位元組序列構成,不攜帶任何語義資訊。透過檔案讀介面(如 InputStream),系統可以順序或隨機地讀取檔案中的位元組資料,而無需理解其具體含義。
4.3 邏輯儲存結構
邏輯儲存結構關注資料的組織和壓縮方式,共包含以下幾個層次:

4.3.1 File

由 file footer、postscript 和若干個 Stripe 構成。
  • Postscript:位於檔案最末端,可以定位檔案內部主要區域的位元組位置。
  • File Footer:儲存檔案級別的 min-max 統計資訊、壓縮與加密演算法資訊、版本資訊等。

4.3.2 Stripe

每 64MB 形成一個 Stripe,由 StripeFooter 和一組 Column 構成。
  • StripeFooter:
    • 存放所有 Column-Stream 兩層結構的元資料資訊。
    • 存放 Stripe 級別的 SMA 資訊(min-max、count、sum)。
    • 存放 Stripe 級別的自定義索引。

4.3.3 Column

每個 Column 由若干個 Stream 構成,儲存結構上是連續的。
  • Index Stream:儲存該列的 row index,由 ColumnStatistics 和 positions 兩部分組成。
    • ColumnStatistics:該列在每個 RowGroup 上的 min-max、count、sum 等 SMA 資訊。
    • Positions:該列在每個 RowGroup 上的起始位元組位置,壓縮前後位元組大小等資訊。
  • Present Stream:儲存該列的 null 值點陣圖,資料經過輕量壓縮和通用壓縮。
  • Data Stream:儲存該列的資料,資料經過輕量壓縮和通用壓縮。
  • Dictionary Stream:對於可以形成字典的列,額外產生一個 Stream 來存放 Dictionary 資料。

4.3.4 Stream

每個 Stream 代表一個連續的位元組儲存區域,包含不同型別的資料流資訊。每個 Stream 由若干個 RowGroup 構成,每個 RowGroup 預設儲存 10,000 行資料。

4.3.5 RowGroup

在每個 Stream 上,每 10,000 行資料形成一個 RowGroup。每個 RowGroup 經過輕量級壓縮和通用壓縮,儲存在 RowIndex 中。RowIndex 描述了 RowGroup 的起始位元組位置、壓縮後大小、原始大小以及起始元素個數等資訊。

4.3.6 通用壓縮

ORC 預設的通用壓縮方式是 Zlib,壓縮率較高但解壓速度較慢。PolarDB-X 列存採用了 LZ4 壓縮,雖然壓縮率一般,但解壓速度更快,適用於高效能需求的場景。

4.3.7 RL(輕量級)壓縮

RL(RunLength)壓縮根據資料的 workload 特徵,選擇靈活的壓縮方式來提高壓縮效率。主要有四種方式:
  1. DIRECT:對於工作負載無明顯特徵的資料,直接儲存原始資料。
  2. SHORT_REPEAT:當資料元素重複且長度符合一定範圍時,採用該編碼。
  3. PATCHED_BASE:當元素的分佈範圍較大且無法使用 DIRECT 編碼時,採用 patch base 編碼。
  4. DELTA:當元素符合特定規律(如等差數列)時,採用 delta 編碼。
4.4 計算結構與 BlockCacheManager
透過檔案、列、Stripe、RowGroup 四級資訊,可以得到具體型別的 Block。例如,如果 RowGroup 步長為 10,000 行,且 chunk limit 為 1000,則 {file=xxx.orc, StripeId=0, ColumnId=1, rowGroupId={1,3}} 可以獲取到 20 個 IntegerBlock 物件。
BlockCacheManager 負責將這些物件快取起來,主要介面包括:
  • 根據 RowGroup 訪問特性,快取所屬 RowGroup 的所有 Chunk。
  • 管理快取的插入、查詢和淘汰。
五、ORC讀取鏈路概述
基於對ORC資料結構層次的深入分析與設計,新的ORC讀取鏈路旨在實現以下目標:
  1. 輸入目標:指定需要讀取的列及其對應的行組資訊。
  2. 輸出目標:生成一組資料塊(Block),並將這些資料塊管理到快取管理器中,以便後續高效訪問。
整個讀取過程主要由兩個核心介面驅動:StripeLoader介面ColumnReader介面,它們各自承擔不同的職責,確保資料的高效讀取與解析。
5.1 IO過程的主入口
StripeLoader介面負責管理ORC檔案中的流(Stream)資訊,並協調IO操作的執行。其主要流程如下:
  1. 初始化流資訊:首先,透過呼叫初始化方法,獲取當前資料條帶(stripe)中所有相關流的資訊,並由流管理器(StreamManager)進行統一維護。
  2. 獲取位元組區域連結串列:在載入過程中,根據指定的列和相應的行組,定位每個行組在物理儲存中的位元組起始位置及其壓縮後的大小。基於這些資訊,為每個行組建立一個緩衝區節點(buffer chunk),並將相鄰的行組透過連結串列形式組織起來,形成一個連貫的位元組區域連結串列。
  3. 非同步讀取資料:透過非同步執行緒,啟動資料讀取操作,並返回一個未來結果物件(CompletableFuture),包含各個流對應的輸入流(InStream)。接下來,執行後續的合併與讀取步驟。
  4. 合併與IO策略:利用資料讀取介面(DataReader),對緩衝區連結串列進行合併處理,將相鄰或距離較近的節點合併為一次IO操作,以減少IO次數,提高讀取效率。
  5. 執行IO操作:資料讀取介面透過檔案系統或輸入流介面,將合併後的IO結果寫回各個連結串列節點中,完成實際的資料讀取。
  6. 組裝輸入流物件:根據每個連結串列節點所屬的不同流,劃分並組裝成多個輸入流物件(InStream),以供後續解析使用。
整個連結串列結構涵蓋了連結串列指標、位元組區域資訊及實際的物理位元組資料。這些資訊由StreamInformation管理,確保流與緩衝區節點之間的關係清晰、可控。
5.2 解析與生成Block
ColumnReader介面承擔將StripeLoader獲取的ORC原始位元組資料,解析為執行器所需的Block物件的任務。其解析過程具有以下特點:
  1. 最佳化記憶體操作:儘量減少記憶體的複製與分配次數,並將資料寫入過程最佳化為連續的記憶體複製,提高執行效率。
  2. 直接資料轉換:建立ORC位元組資料到執行器資料格式的直接轉換路徑,避免引入中間格式(如VectorizedBatch)帶來的額外開銷。
  3. 非同步IO管理:管理非同步IO操作的回撥,根據IO結果和流資訊,自動建立多個解析器,確保資料解析過程的高效與並行。
  4. 緩衝區回收:自動回收非同步IO操作佔用的緩衝區,避免記憶體洩漏,提升系統的資源利用率。
ColumnReader介面主要包含三個核心方法:
  • 初始化方法:用於初始化資料解析過程,可以透過傳入StripeLoader載入返回的回撥,或內部直接觸發載入過程並同步等待結果。
  • 指標定位方法:將內部的讀取指標移動到指定行組及元素的位置,使其具備隨機訪問的能力,便於高效定位和讀取特定資料。
  • 資料讀取方法:從當前指標位置開始,讀取指定數量的資料元素,並將其寫入到隨機訪問的Block中。這種方式避免了動態陣列擴容和邊界檢查等額外開銷,提升了資料寫入的效率。
5.3 通用的抽象列讀取器
AbstractLongColumnReader是一個抽象類,適用於處理所有基於整型或長整型表示的資料型別,如整數(int)、大整數(bigint)、小數(decimal64)、日期(date)、日期時間(datetime)等。其主要步驟如下:
  1. 解析器初始化:根據輸入流和流資訊,構建相應的解析器。例如,對於常見的bigint型別,分別基於資料流和存在流構建壓縮和解壓解析器。資料流儲存壓縮後的bigint值,存在流儲存壓縮後的空值資訊。
  2. 多層指標管理:解析器採用三層指標管理機制,
    • 指標1:指向解壓後的資料快取當前位置,用於快速獲取已解壓的數值或布林值。
    • 指標2:指向Lz4解壓快取的位置,用於管理解壓後的中間資料。
    • 指標3:指向原始位元組資料的連結串列位置,管理尚未解壓的原始資料。
  1. 定位指定位置:透過定位方法,根據行組ID和元素位置,計算出具體的位元組起始位置和壓縮大小,並調整各層指標以正確定位到需要讀取的資料位置。
  2. 資料複製與Block生成:透過讀取方法,從當前指標位置開始,按需複製記憶體資料到隨機訪問的Block中。根據資料型別的不同,分配相應型別的Block物件,並在迴圈中解析存在流和資料流,將最終資料寫入Block,完成Block的構造。
透過以上設計,新的ORC讀取鏈路實現了對每一列和每個行組的精確控制,優化了資料讀取和解析的各個環節。StripeLoader介面負責高效管理流資訊和組織IO操作,ColumnReader介面則專注於高效解析原始位元組資料,生成執行器所需的Block物件。結合AbstractLongColumnReader的抽象設計,整個讀取流程不僅具備高效性和靈活性,還能夠適應多種資料型別的處理需求。這種定製化的ORC讀取鏈路設計,為大規模資料處理提供了強有力的支援,顯著提升了資料讀取的效能與可靠性。
六、第三級快取的設計原理
6.1 第三級快取系統的組成
第三級快取系統的管理涵蓋了多個關鍵部分,以確保高效的資料存取和系統性能的最佳化。首先,檔案系統的輸入輸出(IO)流程是系統的基礎,它包括了網路IO和本地持久化快取兩個方面。網路IO負責遠端資料的傳輸,而本地持久化快取則確保資料在本地儲存的可靠性和快速訪問。其次,檔案元資料管理負責維護儲存在物件儲存服務(OSS)例項上的表文件和索引檔案,確保資料結構的有序性和檢索的高效性。第三,資料來源管理則負責配置和載入不同引擎下的資料來源,支援多樣化的資料輸入渠道。最後,檔案訪問的排程透過大規模並行處理(MPP)框架在執行前對檔案訪問進行合理的分配與排程,最佳化資源利用和訪問效率。
6.2 Hadoop檔案系統與快取檔案系統

6.2.1 Hadoop檔案系統

在Hadoop生態系統中,Hadoop檔案系統提供了一種統一的檔案系統抽象,透過實現其核心抽象類,可以將各種不同的資料來源進行封裝。這種設計使得不同型別的資料來源能夠透過統一的介面進行訪問,大大簡化了檔案操作的複雜性。Hadoop檔案系統透過初始化連線、開放檔案流以及建立檔案流等操作,實現了對檔案的讀取和寫入,從而支援了分散式資料儲存和高效的資料處理。

6.2.2 快取檔案系統

為了進一步提升檔案訪問的效率,PolarDB-X設計並實現了一種快取檔案系統,該系統能夠同時支援本地持久化快取和遠端OSS例項的檔案訪問。該快取檔案系統由兩個主要元件構成:一個負責處理遠端OSS例項上的檔案訪問,另一個則負責本地快取的管理,包括資料的持久化和快取檔案的維護。透過這種雙層快取機制,系統能夠在保證資料一致性的同時,大幅提高檔案訪問的速度和系統的整體效能。
6.3 讀取流程
第三級快取系統的讀取流程經過精心設計,以確保資料的高效獲取和快取管理。首先,系統透過快取檔案系統獲取目標檔案的輸入流,準備進行資料讀取。接下來,系統根據需要讀取檔案的特定位元組範圍,這一過程能夠靈活地訪問檔案的任意位置,滿足不同的資料需求。讀取到的資料隨後由快取管理器進行快取處理,快取管理器內部的非同步刷盤執行緒會定期將從遠端獲取的資料持久化到本地磁碟,並維護一個範圍對映,記錄每個快取塊的遠端路徑、偏移量和長度資訊。當快取中存在相鄰或重疊的資料塊時,系統會觸發檔案合併操作,將多個小檔案合併為一個連續的檔案,以最佳化儲存和訪問效率。同時,後臺的快取統計執行緒和檔案清理執行緒會定期評估快取的大小,並清理過期的快取,確保快取系統的健康和高效執行。
6.4 快取管理策略

6.4.1 執行緒池設計

快取管理器內部設計了多個執行緒池,以協調不同的快取管理任務。首先是非同步刷盤執行緒池,負責將網路IO獲取的資料定期持久化到本地磁碟中,並維護快取的範圍對映。該執行緒池在發現快取位置相鄰或重疊時,會自動觸發檔案合併操作。其次,快取統計執行緒池定期統計已持久化的本地快取檔案的總大小,確保快取總量不會超過預設的上限,從而防止資源過度消耗。最後,快取檔案清理執行緒池則根據最近最少使用(LRU)的策略,標記並清除那些不再需要的過期快取檔案,保持快取系統的高效和整潔。

6.4.2 熱快取與普通快取

快取管理器內部將快取劃分為熱快取和普通快取兩種型別,以充分利用資料的訪問區域性性原則。熱快取用於儲存最近三秒內訪問過的資料,這些資料尚未進行持久化,完全儲存在記憶體中,因而具有更快的訪問速度,適用於需要頻繁訪問的資料場景。普通快取則負責儲存已經持久化到本地磁碟的快取資料,適用於長期儲存和較少訪問的資料。透過這種分層快取策略,系統能夠在保證資料訪問速度的同時,合理利用儲存資源,顯著提升整體查詢效能和系統響應速度。
七、執行快取的反壓機制
7.1 反壓機制的背景
在基於時間片機制和 Pipeline 執行框架的資料庫系統中,任務的生產者和消費者速度差異可能導致記憶體不足或系統過載等問題。為了解決這一問題,PolarDB-X 引入了一套反壓機制,用於控制生產者和消費者的工作流程,避免頻繁的反壓和排程開銷導致系統性能下降。
7.2 Pipeline 執行框架
Pipeline 執行框架(Execution Pipeline)是指在資料庫查詢處理中,將執行計劃的元件或操作以流水線的形式組織和執行的一種策略。Pipeline 執行允許在一個階段處理當前資料批次時,下一個階段可以同時處理上一個階段的輸出結果,從而提高查詢的整體效能和並行度。
主要概念包括:
  • Driver:Pipeline 執行框架中的執行和排程最小單元,負責處理一組運算元的執行任務。
  • Chunk:Pipeline 執行中的資料單元,Driver 產生和消費的資料塊。
  • 生產者與消費者:生產者 Driver 負責產生 Chunk,消費者 Driver 負責消費 Chunk。
7.3 反壓機制的設計
反壓機制主要用於解決生產者速度遠大於消費者速度的問題,防止記憶體水位迅速上升導致系統崩潰。具體設計如下:

7.3.1 生產者-消費者-緩衝區模型

  • 生產者 Pipeline:並行度為 m,每個並行度上形成一個生產者 Driver。
  • 消費者 Pipeline:並行度為 n,每個並行度上形成一個消費者 Driver,擁有一個 LocalBuffer 用於接收生產者的 Chunk。
  • LocalBuffer:以無界非阻塞佇列的形式存在,透過連結串列結構管理 Chunk 的讀寫。
在不考慮記憶體限制的情況下,生產者和消費者可以自由地對 LocalBuffer 進行讀寫。然而,當生產者速度遠超消費者速度時,記憶體水位會迅速上升,造成系統不穩定。因此,引入反壓機制進行控制。

7.3.2 記憶體計數器與訊號機制

memory_ledger:記錄所有 LocalBuffer 中 Chunk 的總位元組數,作為記憶體水位的指標。
readable 與 writable 訊號:基於 SettableFuture 實現,用於控制生產者和消費者的執行狀態。
  • readable:表示緩衝區可讀的訊號。
  • writable:表示緩衝區可寫的訊號。
透過這兩個訊號,系統可以協調生產者和消費者的執行,避免記憶體過載。

7.3.3 反壓流程

  1. 設定記憶體閾值 Smax:memory_ledger 中記錄的記憶體水位一旦達到 Smax,觸發反壓。
  2. 觸發反壓:生產者在寫入Chunk到LocalBuffer的過程中,如果 memory_ledger 的記憶體水位達到Smax,此時,觸發反壓。上游的所有生產者Driver立即讓出時間片,進入Driver阻塞佇列;
  3. 生產者處理:此時,產生一個 writable 訊號,儲存在memory_ledger中。writable訊號代表著 生產者Driver 對於“緩衝區可寫”對這一事件的等待。writable 訊號註冊了一個回撥,該回調的執行內容是,將生產者Driver從阻塞佇列中取出,放入就緒佇列。
  4. 消費者處理:消費者不斷地從LocalBuffer內部的佇列中取出Chunk,與此同時也呼叫memory_ledger.subBytes 減少記憶體水位值。當大部分消費者Driver的LocalBuffer佇列為空,且memory_ledger記憶體水位值接近於0時,
    1. 產生一個 readable訊號存入memory_ledger中,代表著所有消費者Driver等待“緩衝區可讀”這一事件。readable訊號註冊了一個回撥,該回調的內容是,一旦事件完成,
    1. 如果消費者Driver仍在時間片內等待,則結束等待,讓消費者Driver繼續從緩衝區中讀取Chunk;
    2. 如果消費者Driver已經讓出時間片,處於阻塞佇列中,則將消費者Driver轉移到就緒佇列。
以上兩步都是回撥中定義的內容,在第iii步中觸發。
    1. 消費者Driver從memory_ledger中獲取writable訊號,呼叫 writable.set,代表“緩衝區可寫”這一事件已經完成,並觸發執行註冊回撥中的執行體,將所有生產者Driver從阻塞佇列中取出,放入到優先順序最高的就緒佇列中。
    2. 消費者Driver呼叫 readable.get ,執行緒開始原地等待,直到兩種情況發生:1)timeout時間後超時,消費者Driver讓出時間片,進入阻塞佇列;2)未到達超時時間,“緩衝區可讀”這一事件已完成,則結束等待,繼續從緩衝區讀取Chunk
    3. 一段時間後(通常是幾十納秒)生產者Driver從就緒佇列中,被執行緒組取出進行排程執行,開始生產Chunk並寫入到緩衝區LocalBuffer中。消費者Driver也會結束等待,繼續從緩衝區中讀出Chunk,消費和銷燬Chunk記憶體。
這種機制確保了生產者和消費者的速度匹配,避免記憶體過載,同時減少了頻繁的上下文切換,提升了系統整體效能。
7.4 控制反壓頻率
為了避免過多的反壓和排程開銷,系統採用動態調整緩衝區記憶體上限的方法,控制反壓的頻率。具體步驟如下:
  1. 設定反壓次數上限 R:例如系統預設 R=1000。
  2. 設定初始 Smax 值:例如系統預設為 8MB。
  3. 時間片內動態調整:基於生產者和消費者的讀寫速度,動態調整 Smax 的數值,確保每個時間片內的反壓次數不超過設定的上限 R。
  4. 防止記憶體過大:系統根據資料庫例項的執行時記憶體大小,設定 Smax 的上限值,避免因使用者設定過小 R 值導致 Smax 過大,造成記憶體溢位。
7.5 反壓過程的時間序列
反壓過程可以抽象為以下時間序列:
  1. 正常執行:生產者以速度 v1 寫入資料,消費者以速度 v2 讀出資料,緩衝區記憶體水位 S 隨時間 t 變化。
  2. 觸發反壓:當 S 達到 Smax,生產者 Driver 進入阻塞狀態,消費者 Driver 繼續消費資料。
  3. 解除反壓:消費者 Driver 消費完 Chunk,減少 S,觸發 readable 訊號,喚醒生產者 Driver,恢復正常執行。
  4. 迴圈往復:系統在時間片內不斷調整,確保反壓頻率在可控範圍內,提高系統穩定性和查詢效能。
7.6 反壓機制的優勢
  • 記憶體穩定性:透過記憶體計數器和訊號機制,確保記憶體水位在可控範圍內,防止記憶體溢位。
  • 效能提升:減少頻繁的上下文切換,降低排程開銷,提高系統整體效能。
  • 動態調整:根據實際執行情況,動態調整 Smax 值,適應不同的負載和查詢需求。
八、快取預熱
8.1 介紹
PolarDB-X 列存查詢引擎採用了存算分離架構,計算節點在執行分析型查詢前,會首先檢查本地快取是否命中所需的列存索引資料。如未命中,則需從遠端 OSS 儲存服務拉取資料,這會導致查詢延遲增加。為了解決這一問題,PolarDB-X 提供了快取預熱(Warmup)功能,允許提前將資料載入本地快取,從而在實際查詢時避免從遠端拉取資料,提升查詢效能和穩定性。
8.2 快取預熱與快取管理的區別
  • 快取管理:是一個被動填充的過程,只有在上一級快取未命中時,才從下一層快取或儲存服務中拉取和解析資料,並填充到上一級快取中。
  • 快取預熱(Warmup):是一個主動填充快取的過程,提前將指定的資料放入快取中,屬於對快取管理能力的增強,適用於預先知曉需要查詢的資料。
8.3 適用場景
  1. 穩定業務查詢訪問:適用於查詢訪問模式固定、可預知的業務場景,避免即時查詢時的預熱開銷。
  2. 本地磁碟容量足夠:適用於本地磁碟儲存容量大於待預熱的資料量的情況,確保預熱資料能夠全部快取到本地,避免頻繁的遠端拉取。
8.4 Warmup 語法
PolarDB-X 提供了 WARMUP 語法,用於實現快取預熱。該語法僅在列存只讀例項上生效。
WARMUP [cron_expression]SELECT    select_expr [, select_expr] ...    [FROM table_references]    [WHERE where_condition]    [GROUP BY {col_name | expr | position}      [ASC | DESC], ... ]    [HAVING where_condition]    [ORDER BY {col_name | expr | position}      [ASC | DESC], ...]    [LIMIT {[offset,] row_count | row_count OFFSET offset}]cron_expression:    ('cron_string') | <empty>select_expr:    column_name  | aggregate_function(column_name)  | column_name AS alias_name  | expression

引數說明

  • cron_expression:用於定期執行預熱任務的時間表達式,採用類似 Unix cron 表示式的格式。
  • select_expr:指定需要預熱的列或表示式。
8.5 快取預熱的最佳實踐
  1. 定期預熱常用列
    • 場景:業務寫入流量高,查詢延遲敏感。
    • 示例:
WARMUP('* /2 * * * *') SELECT col1, col2, col3... FROM your_table;
作用:每 2 分鐘對目標表的常用列進行週期性預熱。
  1. 夜間批次預熱
    • 場景:業務需要在夜間對大量資料進行分析查詢。
    • 示例:
WARMUP('30 21 * * *') SELECT col1, col2, col3 ...FROM table1 LEFT JOIN table2 ON table1.id1 = table2.id2LEFT JOIN table3 ON table1.id1 = table3.id3WHERE table1.col_date > '2024-01-01';
作用:每天晚上 21:30 對一天的業務資料進行預熱,確保夜間 22:00 的查詢能快速響應。
  1. 效能測試前預熱
    • 場景:進行專業效能測試,如 TPC-H 或 ClickBench。
    • 示例:
WARMUP SELECT * FROM lineitem;WARMUP SELECT * FROM orders;WARMUP SELECT * FROM customer;WARMUP SELECT * FROM part;WARMUP SELECT * FROM partsupp;WARMUP SELECT * FROM supplier;WARMUP SELECT * FROM region;WARMUP SELECT * FROM nation;
作用:對所有表進行一次性預熱,確保效能測試過程中快取命中率高,測試結果更為精準。
  1. 定時批次預熱
    • 場景:每天凌晨進行運維任務前預熱資料。
    • 示例:
WARMUP('*/5 1-5 * * *') SELECT * FROM lineitem;
作用:每天 01:00 至 05:00 的運維視窗期間,每 5 分鐘預熱一次指定表的資料,確保運維期間的查詢效能穩定。
8.6 總結
快取預熱功能透過主動填充快取,消除了查詢時從遠端拉取資料的開銷,大幅提升了查詢效能和穩定性。透過合理配置預熱策略,結合業務訪問模式和查詢需求,PolarDB-X 能夠為使用者提供更加高效和可靠的資料查詢體驗。
九、結語
在大資料時代,如何高效地儲存和查詢海量資料成為了關鍵性的問題。PolarDB-X 列存查詢引擎透過分層快取管理和精細化的反壓機制,有效地優化了 ORC 列存查詢的效能,解決了在大規模資料集下查詢速度瓶頸的問題。同時,靈活的快取預熱策略和最佳化的 ORC 讀鏈路設計,使得 PolarDB-X 能夠在複雜多變的業務場景中,提供高效、穩定的查詢服務。
  1. 高效的多級快取管理:透過分層快取機制,實現了高頻資料的快速訪問和低頻資料的有效管理,顯著提升了查詢效能。
  2. 精細化的反壓機制:透過記憶體計數器和訊號機制,精準控制生產者和消費者的執行流程,防止記憶體過載,確保系統穩定性。
  3. 靈活的快取預熱策略:支援多種預熱場景和策略,滿足不同業務需求,進一步提升查詢響應速度。
  4. 最佳化的 ORC 讀鏈路設計:繞過官方 Reader,採用自主最佳化的讀鏈路,實現了高效的資料讀取和解析,降低了查詢延遲。
  5. 適應性強的存算分離架構:計算節點與儲存節點分離,實現了資源的高效利用和系統的可擴充套件性,適應不同規模和複雜度的查詢需求。
PolarDB-X 透過持續的技術創新和最佳化,致力於為使用者提供更高效、更可靠的資料查詢解決方案,在大資料時代,助力企業實現資料價值的最大化。
RDS MySQL遷移至PolarDB MySQL版
RDS MySQL的資料可線上即時同步到PolarDB MySQL版,並且升級切換後的PolarDB叢集包含源RDS例項的賬號資訊、資料庫、IP白名單和必要的引數。    
點選閱讀原文檢視詳情。

相關文章