天天看點

openGauss存儲技術(四)——openGauss記憶體引擎

作者:資料庫這點事兒

openGauss記憶體引擎

記憶體引擎作為在openGauss中與傳統基于磁盤的行存儲、列存儲并存的一種高性能存儲引擎,基于全記憶體态資料存儲,為openGauss提供了高吞吐的實時資料處理分析能力及極低的事務處理時延,在不同業務負載場景下可以達到其他引擎事務處理能力的3~10倍。

記憶體引擎之是以有較強的事務處理能力,并不單是因為其基于記憶體而非磁盤所帶來的性能提升,而更多是因為其全面地利用了記憶體中可以實作的無鎖化的資料及索引結構、高效的資料管控、基于 NUMA 架構的記憶體管控、優化的資料處理算法及事務管理機制。

值得一提的是,雖然是全記憶體态存儲,但是并不代表着記憶體引擎中的處理資料會因為系統故障而丢失。相反,記憶體引擎有着與openGauss的原有機制相相容的并行持久化、檢查點能力,使得記憶體引擎有着與其他存儲引擎相同的容災能力以及主備副本帶來的高可靠能力。

記憶體引擎總體架構如圖13所示。

openGauss存儲技術(四)——openGauss記憶體引擎

圖13 記憶體引擎總體架構圖

可以看到,記憶體引擎通過原有的 FDW(Foreign Data Wrapper,外部資料封裝器) 擴充能力與 openGauss 的優化執行流程互相動,通過事務機制的回調以及與 openGauss相相容的 WAL機制,保證了與其他存儲引擎在這一體系架構内的共存,保 證了整體對外的一緻表現;同時通過維護内部的記憶體管理結構、無鎖化索引、樂觀事務 機制來為系統提供極緻的事務吞吐能力。

以下将逐漸展開講解相關關鍵技術點與設計。

(一)記憶體引擎的相容性設計

由于資料形态的不同以及底層事務機制的差别,此處如何與一個以段頁式為基礎的系統對接是記憶體引擎存在于openGauss中的重點問題之一。

此處openGauss原有的 FDW 機制為記憶體引擎提供了一個很好的對接接口,優化器可以通過 FDW 來擷取記憶體引擎内部的元資訊,記憶體引擎的記憶體計算處理機制可以直接通過 FDW 的執行器接口算子實作直接調起,并通過相同的結構将結果以符合執行器預期的方式[比如掃描(Scan)操作的流水線(pipelining)]将結果回報回執行器進行進一步處理[如排序、分組(Groupby)]後傳回給用戶端應用。

與此同時記憶體引擎自身的錯誤處理機制(ErrorHandling),也可以通過與FDW的互動,送出給上次的系統,以此同步觸發上層邏輯的相應錯誤處理(如復原事務、線程退出等)。

記憶體引擎借助 FDW 的方式接近無縫地工作在整個系統架構下,與以磁盤為基礎的行、列存儲引擎實作共存。

在記憶體引擎中建立表(CreateTable)的實際操作流程如圖14所示。

openGauss存儲技術(四)——openGauss記憶體引擎

圖14 記憶體引擎建立表的操作流程圖

從圖中可以看到,FDW 充當了一個整體互動 API的作用。實作中同時擴充了FDW 的機制,使其具有更完備的互動功能,具體包括:

(1)支援 DDL接口;(2)完整的事務生命周期對接;(3)支援檢查點操作;(4)支援持久化 WAL;(5)支援故障恢複(Redo);(6)支援 Vacuum 操作。

借由 FDW 機制,記憶體引擎可以作為一個與原有openGauss代碼架構異構的存儲引擎存在于整個體系中。

(二)記憶體引擎索引

記憶體引擎的索引結構以及整體的資料組織都是基于 Masstree實作的。其主體結構如圖15所示。

圖15 記憶體引擎索引主體結構

圖15很好地呈現了記憶體引擎索引的組織架構。主鍵索引(primary index)在記憶體引擎的一個表中是必須存在的要素,是以要求表在組織時盡量存在主鍵索引;如果不存在,記憶體引擎也會額外生成代理鍵(surrogatekey)用于生成主鍵索引。主鍵索引指向各個代表各個行記錄的行指針(sentinel),由行指針來對行記錄資料進行記憶體位址的記錄以及引用。二級索引(secondaryindex)索引後指向一對鍵值,鍵的值(value)部分為到對應資料行指針的指針。

Masstree作為并行 B+樹(Concurrent B+tree),內建了大量 B+樹的優化政策,并在此基礎上做了進一步的改良和優化,其大緻實作方式如圖16所示。

openGauss存儲技術(四)——openGauss記憶體引擎

圖16 Masstree實作方式

相比于傳統的 B樹,Masstree實際上是一個類似于諸多 B+樹以字首樹(trie)的組織形式堆疊的基數樹(radix tree)模式,以鍵(key)的字首作為索引,每k 個位元組形成一層 B+ 樹結構,在每層中處理鍵中這k 個 字 節 對 應 所 需 的INSERT/LOOKUP/ UPDATE/DELETE流程。圖17為k=8時情況。

openGauss存儲技術(四)——openGauss記憶體引擎

圖17 k等于8時的Masstree

Masstree中的讀操作使用了類 OCC(OptimisticConcurrency Control,樂觀并發控制)的實作,而所有的更新(update)鎖僅為本地鎖。在樹的結構上,每層的内部節點(interior node)和葉子節點(leaf node)都會帶有版本,是以可以借助版本檢查(version validation)來避免細粒度鎖(fine-grained lock)的使用。

Masstree除了無鎖化(lockless)之外,最大的亮點是緩存塊(cache line)的高效利用。無鎖化本身在一定程度避免了 LOOKUP/INSERT/UPDATE 操作互相失效共享緩存塊(invalidat ecacheline)的情況。而基于字首(prefix)的分層,輔以合适的每層中 B+樹扇出(fanout)的設定,可以最大限度地利用 CPU 預取(prefetch)的結果(尤其是在樹的深度周遊過程中),減少了與 DRAM 互動所帶來的額外時延。

預取在 Masstree的 設 計 中 顯 得 尤 為 關 鍵,尤 其 是 在 Masstree 從 根 節 點 (tree root)向葉子節點周遊,也就是樹的下降過程中。此過程中的執行時延大部分由于記憶體

互動的時延組成,是以預取可以有效地提高周遊(masstreetraverse)操作的執行效率以及緩存塊的使用效率(命中)。

(三)記憶體引擎的并發控制

記憶體引擎的并發控制機制采用 OCC,在操作資料沖突少的場景下,并發性能很好。

記憶體引擎的事務周期及并發管控元件結構,如圖18所示。

圖18 記憶體引擎的事務周期及并發管控元件結構

這裡需要解釋一下,記憶體引擎的資料組織為什麼整體是一個接近無鎖化的設計。

除去以上提到的 Masstree本身的無鎖化機制外,記憶體引擎的流程機制也進一步最小化了并發沖突的存在。

每個工作線程會将事務處理過程中所有需要讀取的記錄,複制一份至本地記憶體,儲存在讀資料集(read set)中,并在事務的全程基于這些本地資料進行相應計算。相應的運算結果儲存在工作線程本地的寫資料集(writeset)中。直至事務運作完畢,工作線程會進入嘗試送出流程,對讀資料集和寫資料集進行檢查驗證(validate)操作并在允許的情況下對寫資料集中資料對應的全局版本進行更新。

這樣的流程,是把事務流程中對于全局版本的影響縮小到檢查驗證的過程,而在事務進行其他任何操作的過程中都不會影響到其他的并發事務,并且在僅有的檢查驗證過程中,所需要的也并不是傳統意義上的鎖,而僅是記錄頭部資訊中的代表鎖的數位(lock bit)。相應的這些考慮,都是為了最小化并發中可能出現的資源争搶以及沖突,并更有效地使用 CPU 緩存。

同時讀資料集和寫資料集的存在可以良好地支援各個隔離級别,不同隔離級别可以通過在檢查驗證階段對讀資料集和寫資料集進行不同的審查機制來獲得。通過檢查兩個資料集(set)中行記錄在全局版本中對應的鎖定位(lock bit)以及行頭中的TID結構,可以判斷自己的讀、寫與其他事務的沖突情況,進而判斷自己在不同隔離級别下是否可以送出(commit)或是終止(abort)。同時由于 Masstree的 Trie節點(node)中存在版本記錄,Masstree的結構性改動(insert/delete,插入/删 除)操作會更改相關Trie節點上面的版本号。是以維護一個範圍查詢(Range query)涉及的節點集(node set),并在檢查驗證(validation)階段對其進行對比校驗,可以比較容易地在事務送出階段檢查此範圍查詢所涉及的子集是否有過變化,進而能夠檢測到幻讀(Phantom)的存在,這是一個時間複雜度很低的操作。

(四) 記憶體引擎的記憶體管控

由于記憶體引擎的資料是全記憶體态的,是以可以按照記錄來組織資料,不需要遵從頁面的資料組織形式,進而從資料操作的沖突粒度這一點上有着很大優勢。擺脫了段頁式的限制,不再需要共享緩存區進行緩存以及與磁盤間的互動淘汰,設計上不需要考慮IO 以及磁盤性能的優化[比如索引 B+樹的高度以及 HDD(HardDiskDrive,磁盤)對應的随機讀寫問題],資料讀取和運算就可以進行大量的優化和并發改良。

由于是全記憶體的資料形态,記憶體資源的管控就顯得尤為重要,記憶體配置設定機制及實作會在很大程度上影響記憶體引擎的計算吞吐能力。記憶體引擎的記憶體管理主要分為3 層,如圖19所示。

openGauss存儲技術(四)——openGauss記憶體引擎

圖19 記憶體引擎的記憶體管理示意圖

下面分别對3層設計進行介紹:

(1)第一層為應用消費者層,為記憶體引擎自身,包含了臨時的記憶體使用以及長期的記憶體使用(資料存儲)。(2)第二層為應用對象資源池層,主要負責為第一層對象,如表、索引、行記錄、鍵值以及行指針提供記憶體。該層從底層索取大塊記憶體,再進行細粒度的配置設定。(3)第三層為記憶體管理層,主要負責與作業系統之間的互動及實際的記憶體申請。為降低記憶體申請的調用開銷,互動機關一般在2MB 左右。此層同時也有記憶體預取和預占用的功能。

第三層實際上是非常重要的,主要因為:

(1)記憶體預取可以非常有效地降低記憶體配置設定開銷,提高吞吐量。(2)與 NUMA 庫進行互動的性能成本非常高,如果直接放在互動層會對性能産生很大影響。

記憶體引擎對短期與長期的記憶體使用針對 NUMA 結構适配的角度也是不同的。短期使用,一般為事務或會話(session)本身,那麼此時一般需要在處理該會話的 CPU 核對應的 NUMA 節點上擷取本地記憶體,使得交易(transaction)本身的記憶體使用有着較小的開銷;而長期的記憶體使用,如表、索引、記錄的存儲,則需要用到 NUMA 概念中類似全局分布(interleaved)記憶體,并且要盡量将其平均配置設定在各個 NUMA 節點上,以防止單個 NUMA 節點記憶體消耗過多所帶來的性能下降。

短期的記憶體使用,也就是 NUMA 角度的本地記憶體,也有一個很重要的特性,就是這部分記憶體僅供本事務自身使用(比如複制的讀取資料及做出的更新資料),是以也就避免了這部分記憶體上的并發管控。

(五)記憶體引擎的持久化

記憶體引擎基于同步的 WAL機制以及檢查點來保證資料的持久化,并且此處通過相容openGauss的 WAL機制(即 Transaction log,事務日志),在資料持久化的同時,也可以保證資料能夠在主備節點之間進行同步,進而提供 RPO=0的高可靠以及較小RTO 的高可用能力。

記憶體引擎的持久化機制如圖20所示。

openGauss存儲技術(四)——openGauss記憶體引擎

圖20 記憶體引擎的持久化機制

可以看到,openGauss的 Xlog子產品被記憶體引擎對應的管理器(manager)所調用,持久化日志通過 WAL的寫線程(重新整理磁盤線程)寫至磁盤,同時被 wal_sender(事務日志發送線程)調起發往備機,并在備機 wal_receiver(事務日志接收線程)處接收、落盤與恢複。

記憶體引擎的檢查點也是根據 openGauss自身的檢查點機制被調起。openGauss中的檢查點機制是通過在做檢查點時進行shared_buffer(共享緩沖區)中髒頁的刷盤,以及一條特殊檢查點日志來實作的。記憶體引擎由于是全記憶體存儲,沒有髒頁的概念,是以實作了基于 CALC的檢查點機制。

這裡主要涉及一個部分多版本(partial multi-versioning)的概念:當一個檢查點指令被下發時,使用兩個版本來追蹤一個記錄:活躍(live)版本,也就是該記錄的最新版本;穩定(stable)版本,也就是在檢查點被下發且形成虛拟一緻性點時此記錄對應的版本。在一緻性點之前送出的事務需要更新活躍和穩定兩個版本,而在一緻性點之後的事務僅更新活躍版本,保持穩定版本不變。在無檢查點狀态的時候,實際上穩定版本是空的,代表穩定與活躍版本在此時實際上其值是相同的;僅有在檢查點過程中,在一緻性點後有事務對記錄進行更新時,才需要根據雙版本來保證檢查點與其他正常事務流程的并行運作。

CALC(CheckpointingAsynchronously using Logical Consistency,邏輯一緻性異步檢查點)的實作有下面5個階段:

(1)休息(rest)階段:這個階段内,沒有檢查點的流程,每個記錄僅存儲活躍版本。(2)準備(prepare)階段:整個系統觸發檢查點後,會馬上進入這個階段。在這個階段中事務對讀寫的更改,也會更新活躍版本;但是在更新前,如果穩定版本不存在,那麼在更新活躍版本前,活躍版本的資料會被存入穩定版本。在此事務的更新結束,在放鎖前,會進行檢查:如果此時系統仍然處于準備階段,那麼剛剛生成的穩定版本可以被移除;反之,如果整個系統已經脫離準備階段進入下一階段,那麼穩定版本就會被保留下來。(3)解析(resolve)階段:在進入準備階段前發生的所有事務都已送出或復原後,系統就會進入解析階段,進入這個階段也就代表着一個虛拟一緻性點已經産生,在此階段前送出的事務相關的改動都會被反映到此次檢查點中。(4)捕獲(capture)階段:在準備階段所有事務都結束後,系統就會進入捕獲階段。此時背景線程會開始将檢查點對應的版本(如果沒有穩定版本的記錄即則為活躍版本)寫入磁盤,并删除穩定版本。(5)完成(complete)階段:在檢查點寫入過程結束後,并且捕獲階段中進行的所有事務都結束後,系統進入完成階段,系統事務的寫操作的表現會恢複和休息階段相同的預設狀态。CALC有着以下優點:(1)低記憶體消耗:每個記錄至多在檢查點時形成兩份資料。在檢查點進行中如果該記錄穩定版本和活躍版本相同,或在沒有檢查點的情況下,記憶體中隻會有資料自身的實體存儲。(2)較低的實作代價:相對其他記憶體庫檢查點機制,對整個系統的影響較小。(3)使用虛拟一緻性點:不需要阻斷整個資料庫的業務以及處理流程來達到實體一緻性點,而是通過部分多版本來達到一個虛拟一緻性點。

小結

openGauss的整個系統設計是可插拔、自組裝的,openGauss通過支援多個存儲引擎來滿足不同場景的業務訴求,目前支援行存儲引擎、列存儲引擎和記憶體引擎。其中面向 OLTP不同的時延要求,需要的存儲引擎技術是不同的。例如在銀行的風控場景裡,對時延的要求是非常苛刻的,傳統的行存儲引擎的時 延很難滿足業務要求。openGauss除了支援傳統行存儲引擎外,還支援記憶體引擎。在 OLAP(聯機分析處理) 上openGauss提供了列存儲引擎,有極高的壓縮比和計算效率。另外一個事務裡可以同時包含三種引擎的 DML操作,且可以保證 ACID特性。

繼續閱讀