openGauss數據庫源碼解析系列文章——存儲引擎源碼解析(五)

語言: CN / TW / HK
上篇圖文openGauss數據庫源碼解析系列文章——存儲引擎源碼解析(四) 中,從 內存表的總體架構和代碼概述FDW 進行了分享,本篇將從 內存表的存儲、索引、事務、併發控制、重做日誌、檢查點、恢復 展開詳細介紹。

(三)內存表的存儲

Table類包含管理數據庫中內存表所需的所有項。表由以下組件組成:列、主索引和可選的二級索引。關鍵成員變量説明如表1Table類的關鍵成員變量所示。

表1  Table類的關鍵成員變量

成員變量

描述

tableCounter : std::atomic<uint32_t>

原子表ID

m_tupleSize : uint32_t

原始元組大小(以字節為單位)

m_tableExId : uint64_t  

openGauss提供的外部表ID

m_secondaryIndexes : SecondaryIndexMap

按名稱訪問的二級索引映射

m_rwLock : pthread_rwlock_t  

RW Lock,防止在檢查點/真空期間刪除

m_rowPool : ObjAllocInterface*

row_pool行分配器對象池

m_primaryIndex : MOT::Index*

主索引

m_numIndexes : uint16_t  

正在使用的耳機索引數

m_indexes : MOT::Index**  

索引數組

m_fixedLengthRows : bool

指定行是否具有固定長度

m_fieldCnt : uint32_t

表schema中的字段個數

m_columns : Column**

列數組

Row類包含管理表中的內存行所需的所有項,關鍵成員變量如表2所示。

表2  Row類的關鍵成員變量

成員變量

描述

m_data : uint8_t

保存行數據的原始緩衝區,開始於類的結束位置

m_keyType : KeyType  

使用的鍵類型。

Ÿ Internal –用於內部測試(64位)

Ÿ Surrogate –用於無索引表

Ÿ External –需要從行生成

m_pSentinel : Sentinel*

指向主哨兵的指針

m_rowHeader : RowHeader

OCC行的頭部,包含OCC操作的所有相關信息

m_rowId : uint64_t  

創建行時生成的唯一rowId

m_table : Table*

指向內存管理表的指針

(四)索引
MOT使用索引來高效地訪問數據。MOT索引支持範圍查詢等所有基本操作。由於數據存儲在Row類中,每個MOT索引都按順序使用哨兵來訪問數據。
IndexFactory類提供了創建新索引對象的能力。

作為Table類的一部分,Index抽象類提供了創建和訪問數據索引的能力。索引是否滿足唯一性決定了該索引是否允許插入重複鍵。如圖1所示,描述了一個有三行和兩個索引的MOT表T的結構,其中一個索引是非唯一索引,另一個索引是唯一索引。對於非唯一索引而言,MOT內部通過在插入時用唯一標識符填充每個鍵的方式將鍵視為唯一。在圖2中,MOT將哨兵插入到帶有鍵的唯一索引和帶有鍵+後綴的非唯一索引中。使用哨兵方便了維護操作,因為在進行維護操作時,可以在不接觸索引數據結構的情況下替換行。

 

圖1  唯一、非唯一索引和哨兵

Sentinel類包含指向唯一索引情況下的行數據或非唯一索引情況下主哨兵的指針,還包含一些標誌位和引用計數等支持跨事務併發的信息。每次向索引插入新鍵時都會創建哨兵。例如,對於具有3個索引的表,插入新鍵時將創建3個哨兵,每個索引對應一個哨兵。哨兵和行之間的關係如圖2所示。

圖2  哨兵與行關係

MasstreePrimaryIndex類實現了索引接口。它基於Masstree K/V存儲實現,同時封裝了OT內存分配池,根據對象分配任意大小內存。

IndexIterator抽象類提供了創建迭代器並根據提供的迭代器訪問數據的能力。

(五)事務

事務部分覆蓋了從openGauss映射到MOT的所有支持的DDL/DML操作。

事務與併發控制機制緊密耦合,每個操作都必須通過併發控制管理,並完成相應的行為。MOT基於樂觀併發機制,幾乎不使用鎖,因此每個客户端都有自己的事務視圖,並且不會阻塞DML,與磁盤表對每個非SELECT操作都加鎖的使用方式有顯著區別。

每個局部行都有一個初始狀態,狀態由txn_state_machine管理。txn_state_machine擴展了Silo,支持新操作寫後讀和讀後寫,類似於MESI緩存一致性協議。如圖3所示,MOT將新操作(RD/WR)視為本地緩存中的緩存不命中,並將狀態從無效提升為新狀態。

 

圖3  DML事務狀態機

詳細流程

SELECT具體流程如圖4所示。

 

圖4 SELECT時序圖

(1) 當SELECT操作被髮送到FDW,FDW就會打開一個遊標並將正確的哨兵發送到事務管理器。
(2) 事務管理器檢查哨兵,如果哨兵有效則在緩存中搜索,否則返回未找到該行。
(3) TxnAccess在內部查找哨兵,如果在高速緩存中找到該行則返回該行,並認為是高速緩存命中。
(4) TxnManager評估隔離級別和來自緩存的結果:如果TxnAccess返回了一行,直接將其返回給openGauss;否則以下下兩種情況。

① 隔離級別為READ_COMMITED時生成行的副本並返回給FDW。

② 隔離級別為REPEATABLE_READ時映射緩存中的行,並將緩存的行返回給FDW。

UPDATE具體流程如圖5所示。

 

圖5  UPDATE時序圖

(1) 當UPDATE操作被髮送到FDW,FDW就會打開一個遊標,並將正確的哨兵發送到事務管理器。
(2) 事務管理器檢查哨兵,如果哨兵有效就在緩存中搜索,否則返回未找到該行。
(3) TxnAccess在內部查找哨兵,如果在高速緩存中找到該行則返回該行,並認為是高速緩存命中。
(4) TxnManager評估來自緩存的結果。
① 如果TxnAccess返回了一行,直接將其返回openGauss。
② 如果沒有找到該行,則映射哨兵並返回緩存的行。
(5) openGauss計算返回的行,如果該行與篩選器匹配則openGauss向FDW發送帶有更新數據的更新操作。
(6) TxnManager將行的狀態提升為WR,並用從openGauss接收的新數據更新本地行。
DELETE具體流程如圖6所示。

 

圖6 DELETE時序圖

(1) 當DELETE操作被髮送到FDW,FDW就會打開一個遊標並將正確的哨兵發送到事務管理器。
(2) 事務管理器檢查哨兵,如果哨兵有效就在緩存中搜索,否則返回未找到該行。
(3) TxnAccess在內部查找哨兵,如果在高速緩存中找到該行則返回該行,並認為是高速緩存命中。
(4) TxnManager評估來自緩存的結果。

① 如果TxnAccess返回了一行,直接將其返回openGauss。

② 如果沒有找到該行,則映射哨兵並返回緩存的行。
(5) openGauss計算返回的行,如果該行與篩選器匹配,則openGauss向FDW發送帶有更新數據的刪除操作。
(6) TxnManager將行的狀態提升為DEL,並將本地行標記為已刪除。
INSERT具體流程如圖7所示。

 

圖7 INSERT序列圖

(1) 操作發送到FDW後,FDW使用表API準備插入的行,並將該行發送到事務管理器。
(2) 事務管理器執行以下算法。
對於表中的每個索引執行以下操作。
① 將哨兵插入索引。
② 如果已提交行–中止事務。
③ 如果成功插入行–映射並完成插入。
④ 如果行不存在,如下。
Ÿ 如果已映射-自己插入,則中止。

Ÿ 否則將它映射到本地緩存。

(3) TxnManager對於重複的key返回RC_OK或RC_ABORT。TxnDDLAccess用於緩存和訪問事務性DDL更改。事務中執行的所有DDL都存儲在TxnDDLAccess中,並在事務提交/回滾時應用回滾。假設openGauss負責DDL併發,並確保併發的DDL更改不會並行執行。TxnAccess類用於緩存和訪問事務性DML更改的。在事務中執行的所有DML都存儲在TxnAccess中,並在事務提交/回滾中應用回滾。Access類用於保存單行訪問的數據。AccessParams用於保存當前訪問的參數,為CC管理提供額外的信息。InsItem用於保存行插入請求的數據。

(六)併發控制

MOT採用源自SILO的單版本併發控制(concurrency control,CC)算法,是一種OCC算法。併發控制模塊滿足內存引擎的所有事務性需求,其主要設計目標是為MOT內存引擎提供各種隔離級別的支持。當前支持如下隔離級別。

(1) 讀已提交(READ-COMMITED)。
(2) 可重複讀(REPEATABLE-READ)。

圖8  MOT本地內存和全局內存

圖8顯示了MOT運行事務時的關鍵技術,包括如下內容。
(1) 私有事務內存用於無鎖讀寫,僅在最終提交時使用鎖,低爭用。
(2) 低時延,NUMA感知的本地內存。
(3) 樂觀併發控制:數據鎖最小化,低爭用。
(4) 無鎖自動清理(Auto-Vacuum),無開銷。
(5) 極致優化的Masstree實現。

1. SILO併發控制背景&算法

Silo來自Stephen Tu等人在計算機頂級會議SOSP13上發表的《Speedy Transactions in Multicore In-Memory Databases》,在現代眾核服務器上實現了卓越的性能和可擴展性。Silo的設計完全是為了高效地使用系統內存和高速緩存。例如,它避免了所有集中的爭用點,包括集中事務ID分配。Silo的關鍵貢獻是一種基於樂觀併發控制的提交協議,它支持序列化,同時避免對僅讀取的記錄進行共享內存寫入。Silo可提供與其他可序列化數據庫一樣的保證,而不會出現不必要的可擴展性瓶頸或額外的延遲。
設計MOT的設計原則是通過減少對共享內存的寫入來消除不必要的爭用。Silo按照固定時間間隔的epoch進行時間分段,因此Silo這種OCC的變體可以支持序列化,即在epoch邊界形成自然序列化點。在恢復之後也能通過CSN或週期性更新的epoch實現序列化。Epoch還有助於提高垃圾回收效率並使能快照事務。其他一些設計,如事務ID的設計、記錄覆蓋和支持範圍查詢等,進一步加快了事務執行,同時非中心化的持久化子系統也避免了爭用。

2. 事務ID

SILO的併發控制以事務ID(tansaction ID,TID)為中心,它標識事務並記錄版本,也用作鎖和檢測數據衝突。每個記錄都包含最近修改它的事務的TID。TID為64位整數。每個TID的高位包含一個CSN,CSN等於對應事務提交時間的全局序列號;低三位分別為:Bit 63:鎖定標誌位,Bit 62:最新版本標誌位,Bit 61:不存在狀態標誌位。由於CSN有效長度為61bit,因此MOT忽略了事務ID回捲。另外,與許多系統不同,Silo以分散而非集中的方式分配TID。

3. 數據佈局

Silo中的一條記錄包含以下信息。
(1) 一個64位的TID(MOT使用CSN)。
(2) 記錄數據。提交的事務通常就地修改記錄數據,主要通過減少記錄對象的內存分配開銷來提升短寫的性能。然而,讀者必須使用版本驗證協議以確保已讀取每個記錄數據的一致性版本。

4. 樂觀併發控制的數據庫操作

1) 讀/寫流程
(1) 在索引中搜索行引用。
(2) 將數據免鎖複製到基於類型的本地集,包括讀寫集(Read/Write Set, R/W set)。
(3) 基於本地副本進行處理。
2) 校驗流程
(1) 按主鍵順序對寫集(Write Set)進行排序。
(2) 鎖定寫集中的所有行。
(3) 驗證讀寫集的行。
(4) 驗證本地行CSN是否更改。
(5) 驗證該行是否為該鍵的最新版本(由於存在本地數據,可能並非最新)。
(6) 驗證該行未被其他事務鎖定。
(7) 如果以上任一項驗證失敗,則中止事務。
(8) 否則將更新CSN後的所有寫集中的行復制回去,然後釋放這些行上的鎖。
3) 插入流程
(1) 構造一個CSN=0且狀態為不存在的新行r。
① 添加r到寫集並視為常規更新。
② 生成唯一的鍵k。
(2) 在狀態為不存在的情況下,向樹/索引添加從k → r的映射。
① 如果k已經映射到一個狀態為存在的記錄,則插入失敗。
② 否則在讀階段增大版本號。
4) 校驗流程
(1) 鎖定寫集。
(2) 驗證插入集(insert set)。
(3) 若事務中止,則垃圾回收器記錄狀態為不存在的行。
5) 刪除流程
(1) 在索引中搜索行引用。
(2) 將行映射到本地緩存。
(3) 將本地副本標記為已刪除。
6) 校驗流程
(1) 驗證行保持不變;已刪除的行將被視為更新。
(2) 從索引中刪除行,即將已刪除的哨兵/行放入垃圾回收器中。

圖9  MOT提交協議偽代碼

MOT提交協議偽代碼如圖9所示。

5. 關鍵類和數據結構

併發控制的關鍵類和數據結構如表3所示。

表3併發控制的關鍵類和數據結構簡介

關鍵類和數據結構

描述

OccTransactionManager類

管理整個事務驗證機制,與事務類緊耦合

RowHeader類

每一行的OCC元數據。頭部為64位,包含狀態位和CSN

(七)重做日誌

MOT重做日誌(Redo Log)使用預寫式日誌(write-ahead logging,WAL)技術來確保數據完整性。WAL的核心概念是,內存中的數據和索引的更改只有在記錄下這些更改之後才會發生。因此寫入重做日誌是MOT提交協議的一部分。

如圖10所示,MOT存儲引擎的重做日誌模塊同樣使用openGauss磁盤引擎的日誌接口進行持久化和恢復。這意味着MOT重做數據被寫入相同的XLOG文件,並使用相同的XLOG邏輯。使用與openGauss磁盤引擎相同的日誌記錄接口可確保跨引擎事務的一致性,並減少複製、日誌恢復等模塊的宂餘實現。

圖10  使用相同的XLOG (WAL)基礎架構的openGauss磁盤庫和MOT

1. 事務日誌記錄

與openGauss其他存儲引擎不同,MOT內存引擎僅在事務實際提交時才會寫入重做日誌。因此,在事務期間或事務中止時,數據不會寫入重做日誌。這樣可以減少寫入的數據量,從而減少不必要的磁盤IO調用,因為這種磁盤IO調用很慢。例如,如果在事務期間多次更新同一行,則只將表示已提交行的最終狀態寫入日誌。

由於設計MOT內存引擎時考慮了對接不同的數據庫的可能性,因此如圖11所示,MOT通過抽象的ILogger接口對接重做日誌。

圖11 ILogger接口

2. 日誌類型

設計MOT內存引擎時同樣考慮了支持不同的日誌記錄方式。如圖12所示,MOT當前已實現同步日誌(synchronous redo Log)和同步組日誌(group synchronous redo log)。這是通過RedoLogHandler類實現的。RedoLogHandler封裝了日誌邏輯,在數據庫啟動時初始化。RedoLogHandler可以根據需要擴展實現新的日誌記錄方式。

圖12  RedoLogHandler接口

每個事務管理器對象(TxnManager)都包含一個Redolog類,該類負責在提交時將事務數據序列化到緩衝區中。如圖13所示,該緩衝區被傳輸到RedologHandler以進行日誌記錄。

圖13  使用RedoLogHandler的事務日誌記錄

1) 同步日誌記錄
同步日誌使用SynchronousRedoLogHandler。如圖14所示,這是一個簡單的RedoLogHandler實現,它只將序列化緩衝區委託給ILogger(XLOGLogger),以便將其寫入XLOG。因為在寫緩衝區時,事務被阻塞,所以稱為同步。只有當所有事務數據被序列化並寫入日誌時,提交協議才會繼續。

 

圖14  SynchronousRedoLogHandler

2) 同步組提交日誌記錄

同步組提交日誌由SegmentedGroupSyncRedoLogHandler類實現。它通過將幾個事務分組到一個寫塊(write block)中並一起寫入的方式優化日誌記錄。這種方法在一次調用中收集更多數據,可以最大限度地減少磁盤IO次數。除此之外, SegmentedGroupSyncRedoLogHandler將每個NUMA處理器(socket)的事務分組,以減少跨NUMA處理器的數據傳輸,因為跨NUMA處理器的數據訪問比同一NUMA處理器本地內存訪問慢。

當事務提交時,它將數據序列化到緩衝區中,這個緩衝區被傳輸到SegmentedGroupSyncRedoLogHandler,並放入一個提交組中。提交組(Commit Group)是一組序列化事務緩衝區的集合,這些事務緩衝區將被提交併寫入磁盤。根據不同的配置參數,當一個組被填滿或超過預先配置的時間時,MOT將關閉該組,並將該組內所有緩衝區一起寫入日誌。

圖15描述了將多個事務分組一起寫入的組提交邏輯。

 

圖15  同步組提交對每個NUMA處理器的事務進行分組

3) 異步日誌

MOT暫未開發專用的異步日誌機制,異步日誌是通過在conf配置文件中將synchronization_commit參數設置為“off”來實現的。

3. 關鍵類和數據結構

重做日誌的關鍵類和數據結構如表4所示。

表4  重做日誌的關鍵類和數據結構簡介

關鍵類和數據結構

描述

RedoLog類

負責事務數據序列化的主要類,它是TxnManager類的成員對象。RedoLog的commit方法由TxnManager在事務通過驗證階段並且所有更新的行均已鎖定後在提交協議中調用。在將變更應用到存儲之前,RedoLog將在加鎖後序列化事務數據,持久化到日誌,釋放鎖。RedoLog使用RedoLogHandler將數據寫入日誌。RedoLogBuffer類是序列化緩衝區的一個簡單實現,RedoLog類通過RedoLogBuffer序列化事務操作。RedoLogBuffer的前4個字節預留給緩衝區大小,在序列化時寫入。OperationCode枚舉是支持的事務操作列表。RecoveryManager通過根據OperationCode確定如何解析數據並應用事務操作

RedoLogWriter

用於將操作序列化到緩衝區中。RedoLogWriter是一個簡單的helper類,獲取數據和緩衝區,並將數據序列化到緩衝區中

EndSegmentBlock

控制塊,寫在每個flushed緩衝區的末尾。包括事務、CSN、是否提交等信息

EndSegmentBlockSerializer

一個簡單的helper類,用於序列化和反序列化EndSegmentBlock

Ilogger接口

寫日誌接口的抽象。MOT通過ILogger可以寫入不同類型的日誌

LoggerFactory

一個工廠類,用於創建不同類型的ILogger。LoggerFactory通過MOT配置項確定要創建哪種logger

XLOGLogger

基於openGauss XLOG (WAL)的ILogger的一個實現,它簡單地使用openGauss WAL接口將序列化的事務寫入WAL中。MOT WAL日誌項有自己的資源管理器,這可以使openGauss識別到該日誌項是一個MOT WAL日誌項,並將其轉發到MOT處理。XLGLogger使用openGauss日誌基礎能力。因此,AddToLog是一個對openGauss XLOG接口的簡單委託

圖16所示代碼為XLOGLogger::AddToLog接口的實際實現。

 

圖16  XLOGger對openGauss XLOG的委託

RedoLogHandler是重做日誌邏輯的抽象。RedoLogHandler的派生類可實現不同的日誌方法。RedoLogHandler是一個單例模式,由MOT管理,為RedoLog所用。

RedoLogHandlerFactory用於創建RedoLogHandler。MOT根據配置項中配置的RedoLogHandlerType創建RedoLogHandler。

SynchronousRedoLogHandler簡單地將RedoLogBuffers委託給ILogger,以便寫入重做日誌。請參閲前述的同步日誌記錄小節。

GroupSyncRedoLogHandler是最先進的無鎖組提交RedoLogHandler。GroupSyncRedoLogHandler將幾個事務的redo log緩衝區分組到一個組,並把他們寫在一起,以便優化和最小化磁盤IO。請參閲前述同步組提交小節。CommitGroup表示將一組RedoLogBuffer一起記錄。一個提交組有一個主線程,由該主線程創建該提交組,它負責將組內的所有RedoLogBuffer寫入日誌。主線程寫日誌時,所有其他線程都在等待。主線程完成寫入後將發送信號來喚醒組內其他所有線程,一旦喚醒,事務就可以繼續。SegmentedGroupSyncRedoLogHandler是配置了GroupCommit日誌方法時的RedoLogHandler。它是RedoLogHandler的一個實現,每個socket都有GroupSyncRedoLogHandler。SegmentedGroupSyncRedoLogHandler的優點在於可以通過維護多個組提交處理程序實現更高的併發。SegmentedGroupSyncRedoLogHandler維護一個GroupSyncRedoLogHandler數組,並將線程綁定到Socket以將線程委託給正確的處理程序。

(八)檢查點

與openGauss磁盤存儲引擎不同,MOT存儲引擎不基於頁面存儲數據,因此MOT的檢查點機制與磁盤引擎的檢查點機制完全不同。MOT檢查點機制基於CALC(checkpointing asynchronously using logical consistency,使用邏輯一致性異步檢查點)算法,該算法出自耶魯大學Kun Ren等人在數據庫頂級會議SIGMOD 2016發表的《Low-Overhead Asynchronous Checkpointing in Main-Memory Database Systems》。 

1. CALC算法

CALC算法的優點如下。

(1) 內存佔用少:任意時刻每行最多2個副本。只有當檢查點為活動狀態時,更具體地説,僅在檢查點的一個特定階段,才會創建第二個副本,從而減少了內存佔用。
(2) 開銷小:CALC比其他異步檢查點算法開銷小。
(3) 使用虛擬一致性點:CALC不需要停止數據庫就能實現物理一致性。虛擬一致性點是數據庫的視圖,它反映了在指定時間之前提交的所有修改,而不包含指定時間之後提交的修改,而且在不停止數據庫系統的情況下就可以獲得。實際上,可以通過部分多版本創建虛擬一致性點。
如圖1所示,精確部分多版本的總體思想如下。
(1) 每行都與兩個版本相關聯,一個是活動版本,一個是穩定版本。通常,穩定版本為空,表明穩定版本與活動版本一致,檢查點線程可以安全地記錄實時版本。穩定版本僅在檢查點的一個特定階段創建,此時檢查點線程將記錄該穩定版本。
(2) 每行維護一個穩定狀態位,指示穩定行的狀態。

 

圖17檢查點概述

MOT檢查點算法在五個狀態之間循環,如圖18所示。

 

圖18  檢查點狀態機

通常,在進入下一階段之前,系統要等待所有上一階段開始提交的事務完成。
1) REST階段:初始階段,不進行checkpoint。
(1) 在REST階段,每行只存儲一個活動版本。所有穩定版本都為空,穩定狀態位始終為不可用(not available)。
(2) 在此階段開始提交的任何事務將直接對行的活動版本進行操作,並且不會創建穩定版本。
2) PREPARE階段:這是虛擬一致性點之前的階段。當openGauss要求MOT創建快照時,系統從Rest階段移動到Prepare階段。
(1) 與Rest階段類似,每行只存儲一個活動版本。所有穩定版本都為空,穩定狀態位始終為不可用。
(2) 在此階段開始提交的任何事務將直接對行的活動版本進行操作,不會創建穩定版本。
3) RESOLVE階段:該階段標識出虛擬一致性點。在此時間點之前提交的所有事務都將包含在此檢查點中,而隨後提交的事務將不包含在檢查點中。一旦在Rest階段開始提交的事務完成,系統將自動從Rest階段變為Resolve階段。
(1) 在此階段不允許任何事務啟動提交,以避免這些事務在openGauss佔用檢查點的重做點前寫入重做日誌。
(2) 一旦在Rest階段開始提交的事務完成,MOT將在此階段獲取要包含在此檢查點中的任務列表。
4)CAPTURE階段:在此階段中,後台工作進程將數據刷入磁盤。Resolve階段一直持續,直到在準備階段已開始的所有事務完成並釋放其所有鎖為止。系統準備任務列表,然後進入Capture階段。
(1) 在Capture階段開始的事務已經在一致性點之後開始,因此他們肯定會在一致性點之後完成。因此,除非記錄已經具有顯式穩定版本,否則總是在更新前將活動版本複製為對應的穩定版本。
(2) 收到BEGIN_CHECKPOINT事件後,系統生成檢查點工作進程,掃描所有記錄,並將沒有顯式穩定版本的行,或活動版本的對應的穩定版本刷盤。在此過程中,顯式穩定版本一旦刷盤就會被釋放。
5) COMPLATE階段:這是緊跟捕獲階段完成的階段。檢查點捕獲完成後,系統進入Complate階段。事務寫入行為恢復為與Rest階段相同的狀態。
與Rest階段類似,每行只存儲一個活動版本。所有穩定版本都為空,穩定狀態位始終為不可用(not available)。

一旦在捕獲階段開始的所有事務都完成,系統將轉換回Rest階段,並等待下一個觸發檢查點的信號。但是,在返回到Rest階段之前,調用函數SwapAvailableAndNotAvailable翻轉穩定狀態位。這允許MOT避免只能通過完全掃描來重置穩定狀態位,因為在Capture階段之後,所有穩定狀態位都可用,但在Rest階段開始時,希望所有穩定狀態位都不可用。

2. 詳細流程

(1) 一旦觸發了檢查點,Checkpointer後台會觸發MOT的CREATE_SNAPSHOT事件。
(2) 當檢查點處於Rest階段時,CheckpointManager將等待在Complete階段啟動的事務完成。
(3) CheckpointManager修改checkpoint階段為Prepare。如果沒有在Rest階段啟動提交的事務處於活動狀態,則立即進入Resolve階段,否則等待Rest階段啟動提交的最後一個事務完成後進入Resolve階段。
(4) Resolve階段標記了虛擬一致性點。在此階段不允許任何事務開始提交,以避免在openGauss採取檢查點的重做點之前這些事務寫入重做日誌。CheckpointManager等待在Prepare階段啟動的事務完成。
(5) CheckpointManager準備要flush的表的列表(任務列表)並讀取這些表的鎖狀態。
(6) 然後獲取寫鎖,鎖定redolog handler,並將檢查點階段更改為Capture。這標誌着CREATE_SNAPSHOT事件結束。
(7) openGaussCheckpointer獲取WalInsertLock鎖並計算此檢查點的重做點。然後,該重做點觸發MOT的SNAPSHOT_READY事件。
(8) CheckpointManager存儲重做點,釋放redolog handler鎖。這標誌着SNAPSHOT_READY事件結束。
(9) 然後openGaussCheckpointer釋放WalInsertLock並將所有磁盤引擎髒頁刷盤,即磁盤引擎的檢查點。
(10) 然後觸發MOT的BEGIN_CHECKPOINT事件。
(11) CheckpointManager在這個階段生成檢查點worker來完成MOT檢查點任務列表。
(12) 檢查點worker之間共享任務列表,並將所有符合條件的行刷入磁盤(行的穩定版本或沒有顯式穩定版本到磁盤的活動版本)。在此過程中任何顯式穩定版本一旦刷新到磁盤,就會釋放。
(13) 一旦所有檢查點worker完成任務,CheckpointManager將解鎖表並清除任務列表。
(14) CheckpointManager還可將檢查點階段提前到Complate。
(15) 通過創建map文件、結束文件等來完成檢查點,然後更新mot.ctrl文件。
(16) 等待Capture階段開始的事務完成。
(17) 交換可用位和不可用位,以便將他們映射到穩定狀態位中的1和0值。
(18) 修改checkpoint階段為Rest。這標誌着BEGIN_CHECKPOINT事件和MOT檢查點的結束。
(19) 然後openGauss將檢查點記錄插入到XLOG中,flush到硬盤,最後更新控制文件。openGauss中的檢查點就此結束。

3. 關鍵類和數據結構

檢查點的關鍵類和數據結構如表5所示。

表5 檢查點的關鍵類和數據結構簡介

關鍵類和數據結構

描述

CheckpointManager類

理整個檢查點機制的主類,是MOT中所有檢查點相關任務的接口類。CheckpointWorkerPool類用於生成檢查點worker並刷盤。該類不提供真正的接口。實例化對象時會生成檢查worker,並使用回調通知任務完成。在Capture階段CheckpointManager實例化一個CheckpointWorkerPool對象,並簡單地等待所有任務完成。CheckpointControlFile用於實現checkpoint控制文件邏輯。MOT控制文件保存着最後一個有效檢查點ID、lsn(openGauss中的重做點)和最後一個重放lsn。控制文件mot.ctrl在每個檢查點結束時更新

CheckpointWorkerPool類

用於生成檢查點worker並刷盤

(九)恢復

恢復部分有兩個目的,一是在崩潰或關機後達到最新的一致狀態,也稱為冷啟動(coldstart),二是在HA複製場景中,在備機側通過重放redo log完成複製。

冷啟動時,當所有WAL記錄都重放完成後恢復結束;但在HA複製場景中,複製將持續進行,直到備機改變狀態。

在恢復過程中,可能存在跨越多個重做日誌段的長事務,MOT將其保存在InProcessTransactions映射對象中,直到提交。包含在映射中的數據作為檢查點處理過程的一部分進行序列化,並在檢查點恢復期間進行反序列化。

此外,在最後恢復階段,完成所有檢查點/WAL記錄之後將設置最後的CSN,並將代理鍵生成器恢復到崩潰或關閉前的最新狀態。

為了恢復代理狀態,每個恢復線程(檢查點和重做日誌)都在更新每個線程id的代理最大鍵數組。最後,這些數組被合併成單個數組,用於恢復最後狀態。

1. 詳細流程

具體恢復流程如圖19所示。

 

圖19  恢復時序圖

恢復過程如下。
(1) 通過openGauss的StartupXLOG函數調用MOT恢復過程。
(2) 如果存在檢查點則從檢查點執行第一次恢復。
(3) 讀取控制文件,並獲取重放LSN。當重啟點(備機檢查點)存在時,將使用lastReplayLsn作為重放點,而非LSN。
(4) 處理檢查點映射文件並生成任務列表。由於MOT檢查點僅由行組成,不需要以特定順序重放,因此這些行的恢復可以並行執行。這就是檢查點進程將表拆分成段的原因。
(5) 從元數據文件中恢復所有表的元數據。
(6) 創建檢查點恢復線程,每個線程嘗試從列表中獲取任務,讀取與此任務關聯的文件並恢復行數據。此恢復是非事務性的。
(7) 如果有進程內事務,也會從檢查點恢復。
(8) 檢查點恢復完成,返回StartupXLOG,開始重放redo記錄。
(9) 當遇到MOT資源管理器(resource manager,rmgr)記錄時,將調用MOT引擎的MOTRedo函數,該函數調用恢復管理器ApplyRedoRecord方法。
(10) 在ApplyRedoRecord中,只有當數據的lsn大於恢復的檢查點lsn,並且調用ApplyLogSegmentFromData時,才會處理數據。
(11) ApplyLogSegmentFromData從數據中提取LogSegment,分配並插入InProcessTransactions Map。
(12) 當遇到提交記錄時,該記錄可以是僅MOT事務的LogSegment的一部分,也可以來自MOT註冊到openGauss的DDL或跨引擎事務的提交後回調。事務的相關日誌段將進行事務性重放和提交。
(13) 上述過程將繼續循環,直到StartupXLOG完成。
(14) 調用RecoverDbEnd完成恢復,設置CSN並應用代理狀態。

2. 關鍵類和數據結構

恢復的關鍵類和數據結構如表6所示。

表6 關鍵類和數據結構簡介

關鍵類和數據結構

描述

RecoveryManager

實現了IRecoveryManager的API,它是恢復中使用的主類

IRecoveryManager

是一個抽象類,定義了每個RecoveryManager應該實現的接口

SurrogateState

保存了內部數組中每個連接ID的代理鍵值

LogStats

用於統計,統計信息在LogStats::Entry中按表收集,需在mot.conf中啟用

CheckpointRecovery

負責從有效的檢查點恢復所有行。它封裝了恢復所需的所有操作,包括表的元數據和並行行插入

LogSegment

實際上是重做日誌保存的數據段,除了緩衝區外還包括描述操作及其事務ID的元數據

RedoLogTransactionSegment

多個日誌段的容器,可以形成一個完整的事務

RedoLogTransactionIterator

一個從XLOG數據中提取LogSegments的helper類

InProcessTransactions

一個獨立的Map對象,它保留正在運行的事務LogSegments,在這些事務提交之前都會收集

RecoveryOps

從LogSegment執行DML和DDL操作的實際恢復

 四、小結

本章主要介紹了openGauss的存儲引擎,包括磁盤引擎和內容表。在磁盤引擎中,openGauss提供不同存儲格式的磁盤引擎來滿足不同業務場景對於數據不同的訪問和使用模式。內存錶針對眾核和大內存服務器進行了優化,可提供非常高的事務性工作負載性能。

 往期推薦 ·


openGauss數據庫源碼解析系列文章——存儲引擎源碼解析(一)

openGauss數據庫源碼解析系列文章——存儲引擎源碼解析(二)

openGauss數據庫源碼解析系列文章——存儲引擎源碼解析(三)

openGauss數據庫源碼解析系列文章——存儲引擎源碼解析(四)

歡迎訪問openGauss官方網站

openGauss開源社區官方網站:

https://opengauss.org

openGauss組織倉庫:

https://gitee.com/opengauss

openGauss鏡像倉庫:

https://github.com/opengauss-mirror

掃碼關注我們

微信公眾號|openGauss

微信社羣小助手|openGauss-bot

本文分享自微信公眾號 - openGauss(openGauss)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閲讀的你也加入,一起分享。