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

語言: CN / TW / HK

上一篇詳細講述了“4.2.5 行存儲索引機制”、“4.2.6 行存儲緩存機制”及“4.2.7 cstore”等精彩內容。本篇我們詳細講述“4.3 內存表”相關內容。

4.3 內存表

MOT(memory-optimized tables,內存表)是事務性、基於行存儲的存儲引擎,針對眾核和大內存服務器進行了優化。MOT是openGauss數據庫的一個先進特性,可提供非常高的事務性工作負載性能。MOT完全符合ACID要求,並支持嚴格的持久性和高可用性。企業用户可以將MOT用於關鍵任務、性能敏感的在線事務處理應用程序,以實現高性能、高吞吐量、低且可預測的延遲,並提升眾核服務器的利用率。

4.3.1 總體架構和代碼概述

MOT引擎架構概述如4-43所示。
在這裏插入圖片描述

圖4-43 MOT總體架構

MOT的關鍵技術包括下面幾個內容。
(1) 面向內存優化的數據結構。
(2) 樂觀併發控制。
(3) 無鎖索引。
(4) NUMA感知技術,事務性本地內存。
(5) 即時編譯(Just-In-Time,JIT)。
總體而言,MOT的目標是建立一個在眾核CPU架構中表現出色的OLTP系統,特別是性能可以隨核數增加而線性擴展。根據經驗實驗,Masstree無鎖實現和針對Silo(請參閲錯誤!未找到引用源。)的改進是最佳組合。
索引方面,通過比較各種先進解決方案後選擇了Masstree,因為它在點查詢、迭代等方面表現出最佳的整體性能。Masstree是Trie和B+樹的組合,實現了對緩存、預取和細粒度鎖的高效利用。它針對鎖衝突嚴重的情況進行了優化,並在其他先進索引的基礎上增加了各種優化。Masstree索引的缺點是內存消耗較大,雖然每行數據消耗的內存大小相同,但是每行每個索引(主索引或次索引)的內存平均高出16個字節:在磁盤表使用的基於鎖的B樹中為29個字節,而MOT的Masstree為45個字節。
併發控制算法方面,為了從內存架構中獲得優勢,設計考慮上最大限度地提高OLTP事務處理速度。雖然最近有一些內存多版本併發控制方面的改進,但為了避免迅速的垃圾收集,MOT只維護實際數據。MOT的另一個設計選擇是不像HStore那樣對數據進行分區,因為在實際的工作負載中事務跨區時性能會急劇下降。儘管已出現一些新的方法通過靜態和動態分析來調整並行性,但此類方法會增加時延,並引入額外限制。
內存存儲引擎常用的單版本、shared-everything類型的併發控制算法主要分為三類。

  1. 樂觀併發控制(Optimistic Concurrency Control,OCC),樂觀併發控制有三個階段。
    1. 讀取階段,從共享內存中讀取事務記錄,並將所有記錄寫入本地的私有副本。
    2. 驗證階段,執行一系列事務檢查以確保一致性。
    3. 寫階段,驗證成功後,提交該事務;驗證失敗時將中止該事務,不會提交。兩個OCC事務同時執行時不會互相等待。
  2. 遭遇時間鎖定(encounter time locking,ETL)。在ETL中,讀取者是樂觀的,但寫入者會鎖定待訪問的數據。因此,來自不同ETL事務的寫入者會互相看到,並可據此決定是否中止事務。ETL在兩個方面提高了OCC的性能。首先,ETL能儘早發現衝突,而事務處理是有代價的,因為在提交時發現的衝突,需要中止至少一個事務,因此ETL可以在衝突情況下提高事務吞吐量。其次,ETL能夠高效地處理寫後讀(reads-after-writes,RAW)。
  3. 悲觀的併發控制(以2PL為例)。在讀取或寫入時鎖定一行,提交後釋放鎖。這些算法需要一些避免死鎖的方法。死鎖可以通過週期性的計算等待圖(wait-for graph)來檢測,也可以通過保持TSO(total store ordering)中的時間順序或一些其他的規避方案來避免。在2PL算法中,如果一個事務正在寫一行數據,其他事務就不能訪問或寫入該行數據,如果正在讀一行數據,則不允許任何事務寫該行數據,但可以讀取這行數據。
    對於大多數工作負載而言,OCC是最快的選擇。原因之一是當CPU執行多個交互線程時,一個鎖很可能被一個切換出去的線程持有。另外一個原因是悲觀的算法涉及死鎖檢測,這會增大開銷,同時讀寫鎖比標準的自旋鎖效率低。Silo來自Stephen Tu等人在計算機頂級會議SOSP13上發表的《Speedy Transactions in Multicore In-Memory Databases》,可以在現代眾核服務器上實現卓越的性能和可擴展性。MOT最終選擇了Silo,因為它比其他現有的方案,如TicToc更簡單,同時在大多數工作負載下可保持很高的性能。ETL雖然有時比OCC快,但可能會觸發不必要的中止退出。相比之下,OCC只在提交時實際發生衝突時中止退出。
    目前,與業界其他領先的內存數據庫理系統類似,MOT表的數據容量被限制在最大可用內存範圍內。通過操作系統的內存頁面交換技術可以擴展內存範圍,但在這種情況下性能可能會下降。觀察近年來業界出現了幾種技術來緩解這個問題,包括數據重組、反緩存和分層等,這也是MOT未來的工作方向之一。
    與磁盤引擎(包括共享內存等磁盤數據庫的內存優化技術)相比,設計內存引擎的挑戰主要是避免磁盤引擎那樣基於頁面的間接訪問方式。
    MOT存儲引擎代碼位於src/gausskernel/storage/mot目錄下。目錄結構如下。
src/gausskernel/storage/mot/
├── core
├── fdw_adapter
└── jit_exec

MOT文件夾下有三個頂層子目錄。
(1) core:包含MOT引擎的核心模塊,如併發控制、事務管理、內存管理、存儲、檢查點、重做、恢復、事務、基礎設施組件、統計、實用程序等。
(2) fdw_adapter:包含FDW適配器接口和實現。
(3) jit_exec:包含MOT JIT(Just-In-Time)組件。它有兩種實現,一種使用本地LLVM(low level virtual machine),另一種使用TVM(tiny virtual machine),可以在不提供本地LLVM支持的計算機上使用。

4.3.2 FDW

openGauss使用FDW API與內存引擎進行對接。實現上分為兩個層次。
(1) 消費者層——FDW API的實現,它由提供數據管理和操作的靜態函數組成。這些函數通過fdwapi.h中的FdwRoutine結構以回調的形式暴露給上層。
(2) 通信層——連接openGauss其他部分和MOT內部API。這包括數據和數據定義轉換和對MOT內部表示的調整。

1. 消費者層

MOT消費者層FDW API的功能和用途如表4-35所示。其中,計劃、執行階段請參考《SELECT/UPDATE/DELETE(計劃階段)》以及《SELECT/UPDATE/DELETE(執行階段)》兩小節。

表4-35 MOT消費者層FDW接口簡介

 

函數名

使用階段(數字為調用順序)

描述

GetForeignRelSize

計劃1

查詢過程中表的每個實例均調用,以評估大小

GetForeignPaths

計劃2

有索引情況下調用,確定哪些索引可用於從當前查詢中的表中獲取數據

GetForeignPlan

計劃3

創建用於從表中取數據的執行計劃

PlanForeignModify

計劃4

在數據修改查詢時調用。設置數據修改的附加信息

AddForeignUpdateTargets

計劃5

向查詢輸出添加其他結果列

BeginForeignScan

執行1

在數據提取開始時,對查詢中的每個表實例調用

BeginForeignModify

執行1.1

修改查詢

IterateForeignScan

執行2

調用以獲取相應的記錄

ReScanForeignScan

執行3

應重新啟動迭代時調用

EndForeignScan

執行4

調用以完成取數據

EndForeignModify

執行4.1

在通過修改查詢啟動數據掃描時調用

ExecForeignInsert

執行過程

滿足其他條件時調用以完成記錄插入

ExecForeignUpdate

執行過程

滿足其他條件時調用以完成記錄修改

ExecForeignDelete

執行過程

滿足其他條件時調用以完成刪除記錄

ExplainForeignScan

輸出計劃

執行explain時調用以打印詳細計劃信息

AnalyzeForeignTable

分析1

對錶做analyze操作

AcquireSampleRows

分析2

收集採樣信息用來做analyze操作

TruncateForeignTable

截斷

調用以清空表數據

VacuumForeignTable

垃圾回收

清理表

NotifyForeignConfigChange

配置

在數據庫配置更改事件中調用

ValidateTableDef

DDL

查詢數據定義時調用

IsForeignRelUpdatable

信息

調用以確定FDW支持的操作(SELECT/INSERT/UPDATE/DELETE)

GetFdwType

信息

提供FDW類型

GetForeignMemSize

統計信息

以字節為單位提供MOT引擎的內存使用情況

GetForeignRelationMemSize

統計信息

以字節為單位提供存儲表/索引數據的內存使用情況

2. 主要流程時序圖

請注意,為了便於讀者更好的理解正常流程和異常流程的關係,本節中的時序圖均將正常流程和異常流程放在同一張圖中,其中P1、P2……Pn為異常流程。同時,為簡化時序圖幫助理解流程,異常流程僅在異常發生的位置進行標識,未完整繪製異常流程的時序。

1) CREATE表

在這裏插入圖片描述

圖4-44 CREATE表時序圖

用户創建一個新的內存表時,openGauss通過FDW適配器將請求轉發給MOT存儲引擎。創建表的正常流程和主要異常流程如圖4-44所示。
正常事件流:FDW創建一個新的表對象。然後對每個列執行以下操作。
(1) FDW驗證列定義。
(2) MOT引擎進一步驗證列定義。
(3) 創建給定類型的列對象並將其添加到表中。
(4) 對所有列定義重複此過程。
添加完所有列後表定義本身就被驗證,表對象已添加到MOT引擎,並通過鎖保護,最後,由於表還沒有索引,所以會向表中添加一個偽主索引/鍵。DDL命令會持久化到重做日誌中。
P1:在此異常事件流中,列定義失敗時FDW通過ereport函數向openGauss報告無效列定義(invalid column definition)錯誤。
P2:在此異常事件流中,由於以下原因之一,導致表的列定義驗證失敗:①不支持的列類型;②字段大小無效;③資源限制:已超過允許的表最大列數;④資源限制:列的總大小已超過最大元組大小;⑤資源限制:列名大小超過允許的最大值。
P3:在此異常事件流中,由於以下原因之一,導致表的列定義驗證失敗:①資源限制:超出每個表的最大列數;②資源限制:列的總大小超過最大元組大小;③資源限制:列名大小超過允許的最大值。
P4:總元組大小超過了允許的最大元組大小。

2) DELETE表

在這裏插入圖片描述

圖4-45 DELETE表時序圖

如圖4-45所示,用户DELETE內存表時,openGauss通過FDW適配器將請求轉發給MOT存儲引擎。
正常事件流:FDW從MOT引擎中檢索表對象,並將DELETE表的請求轉發給MOT引擎。DDL命令在重做日誌中持久化,然後對於表中的每個索引,索引數據將被截斷並DELETE索引對象。隨後對錶中的每個索引重複此過程。在DELETE所有索引對象之後,MOT將DELETE表對象並返回給FDW。
P1:在此異常事件流中,沒有找到索引所屬的表。此錯誤條件被靜默忽略,FDW不會向openGauss報告錯誤。
P2:在此異常事件流中,在表對象中找不到請求的二級索引。此錯誤條件被靜默忽略,FDW不會向openGauss報告錯誤。

3) CREATE索引

在這裏插入圖片描述

圖4-46 CREATE索引時序圖

如圖4-46所示,用户希望在現有的內存表中創建新索引時,openGauss通過FDW適配器將請求轉發給MOT存儲引擎。
正常事件流:FDW從MOT引擎中檢索表對象並創建一個索引對象。然後,對每個列執行以下操作:①FDW驗證列大小;②FDW驗證列類型。對所有列定義重複此過程。驗證所有列之後,生成的鍵大小也會被驗證。在創建主索引時,原創建表階段時添加的偽主索引將被新的主索引替換,應當在表仍然為空時完成。否則,將向表添加二級索引。索引數據本身是由主索引數據創建的。最後,整個DDL命令將持久化到重做日誌。
P1:在此異常事件流中,不支持索引類型,FDW通過ereport工具向openGauss報告未支持的特性(feature unsupported)錯誤。目前只支持BTREE索引類型。
P2:在此異常事件流中,列大小驗證失敗,FDW通過ereport實用程序向openGauss報告無效列定義錯誤。
P3:在此異常事件流中,列類型驗證失敗,FDW通過ereport實用程序向openGauss報告未支持的特性(feature unsupported)錯誤。
P4:在此異常事件流中,索引的總鍵大小超過了最大允許的鍵大小,FDW通過ereport工具向openGauss報告無效列定義錯誤。
P5:在此異常事件流中,由於資源限制(內存不足),無法執行操作。
P6:在此異常事件流中,由於以下原因之一無法執行操作:①資源限制(內存不足),無法執行操作,②唯一主鍵衝突。

4) DELETE索引

在這裏插入圖片描述

圖4-47 DELETE索引時序圖

如圖4-47所示,用户希望DELETE內存表中的現有索引時,openGauss通過FDW適配器將請求轉發給MOT存儲引擎。
正常事件流:FDW從MOT引擎中檢索表對象,並轉發從表中DELETE二級索引的請求。DDL命令在重做日誌中持久化,然後截斷索引數據並DELETE索引對象。
P1:在此異常事件流中,沒有找到索引所屬的表。此錯誤條件被靜默忽略,FDW不會向openGauss報告錯誤。
P2:在此異常事件流中,在表對象中找不到請求的二級索引。此錯誤條件被靜默忽略,FDW不會向openGauss報告錯誤。

5) 截斷表

在這裏插入圖片描述

圖4-48 截斷表時序圖

如圖4-48所示,用户截斷現有的內存表內容時,openGauss通過FDW適配器將請求轉發給MOT存儲引擎。
正常事件流:FDW從MOT引擎中檢索表對象並轉發截斷表的請求。表中每個索引的索引數據被截斷,並且將DDL命令持久化到重做日誌。
P1:在此異常事件流中,沒有找到該表。此錯誤條件被靜默忽略,FDW不會向openGauss報告錯誤。
P2:在此異常事件流中,由於資源限制(內存不足),無法執行操作。

6) INSERT行

在這裏插入圖片描述

圖4-49 INSERT行時序圖

如圖4-49所示,openGauss通過FDW適配器將請求轉發給MOT存儲引擎。可以通過自動提交(auto-commit)INSERT行,也可以在事務中INSERT行。
正常事件流:FDW從MOT引擎中檢索表對象並創建新的行對象。由於內存引擎不同於磁盤引擎,不使用基於頁面的間接訪問形式,因此需要將行格式從openGauss行格式轉換為MOT行格式(MOT將這種行格式轉換稱為Pack,反向轉換稱為unpack)後才能INSERT到表中。隨後為該表的每個索引創建一個鍵。INSERT行的整個請求被傳遞到當前Txn,隨後將該請求轉發到併發控制模塊,並持久化到重做日誌。
P1:在此異常事件流中,由於資源限制(內存不足),無法執行操作。
P2:在此異常事件流中, INSERT行失敗,原因如下:①內存分配失敗;②在主節點上違反了唯一約束。在這兩種情況下,父事務都將使用正確的錯誤代碼中止。

7) SELECT/UPDATE/DELETE(計劃階段)

在這裏插入圖片描述

圖4-50 SELECT/UPDATE/DELETE(計劃階段)時序圖 用户可以在內存表中SELECT/UPDATE/DELETE行。每個操作分為兩個階段:計劃階段和執行階段。圖4-50主要關注計劃階段。 每個SELECT/UPDATE/DELETE的規劃階段包括選擇最佳執行計劃。為此,openGauss準備了幾個可能的執行路徑,並要求FDW估計每個此類路徑的開銷,以便openGauss可以選擇最佳的執行路徑。 正常事件流:openGauss調用GetForeignRelSize接口,FDW從MOT中檢索相關表對象,並用啟動成本和總成本估計初始化此查詢的FDW狀態。openGauss調用GetForeignPaths觸發所有涉及索引對象的開銷計算。最後,openGauss調用GetForeignPlan觸發結束整個過程,包括查詢子句對本地和遠程排序,以及根據所選執行路徑對FDW狀態進行序列化。PREPARE語句的執行在此結束,其他語句待執行的部分將在執行階段中描述。 P1:在此異常事件流中,由於資源限制(內存不足),無法執行操作。

8) SELECT/UPDATE/DELETE(執行階段)

在這裏插入圖片描述

圖4-51 SELECT/UPDATE/DELETE(執行階段)

如圖4-51所示,計劃階段完成後,執行階段開始。
正常事件流:openGauss調用BeginForeignScan,FDW檢索相關表並初始化查詢的FDW狀態。在進行UPDATE/DELETE操作時,openGauss通過調用BeginForeignModify接口觸發一個額外的初始化階段,然後返回NULL。openGauss通過調用IterateForeignScan接口進行如下操作:①僅在需要時一次性初始化遊標;②在當前事務對象中查找下一行;③將行數據從MOT格式轉換為openGauss格式;④遊標前進;⑤返回包含unpack行的槽位。重複此過程,直到遊標中不再有行,並且返回NULL到openGauss。然後openGauss應用本地條件/查詢子句等本地過濾器來決定是否繼續處理該行。在進行SELECT操作時,該行將被添加到結果集中,並返回結果集給用户。進行UPDATE和DELETE操作需執行的其餘部分將在後文中進行描述。

9)UPDATE(結束執行階段)

在這裏插入圖片描述

圖4-52 UPDATE(結束執行階段)時序圖

執行SELECT、UPDATE和DELETE語句的公共部分後,每個語句的剩餘部分有所不同。圖4-52描述UPDATE的剩餘部分。
正常事件流:openGauss為特定的更新元組調用ExecForeignUpdate接口。FDW更新當前事務對象中最後一行的狀態以進行併發控制,然後FDW將行數據從openGauss格式轉換為MOT格式,並通過覆蓋該行的方式完成變更字段的更新。該操作在重做日誌中持久化,並返回openGauss。
P1:在此異常事件流中,由於併發控制事務對象的行狀態更新失敗。在這種情況下,父事務將以適當的錯誤代碼中止。

10) DELETE行(結束執行階段)

在這裏插入圖片描述

圖4-53 DELETE行(結束執行階段)

圖4-53描述了DELETE操作執行的剩餘部分。
正常事件流:openGauss為特定更新的元組調用ExecForeignDelete接口。FDW更新當前事務對象中最後一行的狀態以進行併發控制,然後將操作持久化到重做日誌中,並返回openGauss。
P1:在此異常事件流中,由於併發控制事務對象的行狀態更新失敗。在這種情況下,父事務將以適當的錯誤代碼中止。

4.3.3 內存表的存儲

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

表4-36 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類包含管理表中的內存行所需的所有項,關鍵成員變量如表4-37所示。

表4-37 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*

指向內存管理表的指針

4.4.4 索引

MOT使用索引來高效地訪問數據。MOT索引支持範圍查詢等所有基本操作。由於數據存儲在Row類中,每個MOT索引都按順序使用哨兵來訪問數據。
IndexFactory類提供了創建新索引對象的能力。
作為Table類的一部分,Index抽象類提供了創建和訪問數據索引的能力。索引是否滿足唯一性決定了該索引是否允許插入重複鍵。如圖4-54所示,描述了一個有三行和兩個索引的MOT表T的結構,其中一個索引是非唯一索引,另一個索引是唯一索引。對於非唯一索引而言,MOT內部通過在插入時用唯一標識符填充每個鍵的方式將鍵視為唯一。在圖4-54中,MOT將哨兵插入到帶有鍵的唯一索引和帶有鍵+後綴的非唯一索引中。使用哨兵方便了維護操作,因為在進行維護操作時,可以在不接觸索引數據結構的情況下替換行。
在這裏插入圖片描述

圖4-54 唯一、非唯一索引和哨兵

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

圖4-55 哨兵與行關係

MasstreePrimaryIndex類實現了索引接口。它基於Masstree K/V存儲實現,同時封裝了MOT內存分配池,根據對象分配任意大小內存。
IndexIterator抽象類提供了創建迭代器並根據提供的迭代器訪問數據的能力。

4.3.5 事務

事務部分覆蓋了從openGauss映射到MOT的所有支持的DDL/DML操作。
事務與併發控制機制緊密耦合,每個操作都必須通過併發控制管理,並完成相應的行為。
MOT基於樂觀併發機制,幾乎不使用鎖,因此每個客户端都有自己的事務視圖,並且不會阻塞DML,與磁盤表對每個非SELECT操作都加鎖的使用方式有顯著區別。
每個局部行都有一個初始狀態,狀態由txn_state_machine管理。txn_state_machine擴展了Silo,支持新操作寫後讀和讀後寫,類似於MESI緩存一致性協議。如圖4-56所示,MOT將新操作(RD/WR)視為本地緩存中的緩存不命中,並將狀態從無效提升為新狀態。
在這裏插入圖片描述

圖4-56 DML事務狀態機 圖注:狀態説明: INV:錯誤狀態(INVALID STATE);RD:查詢狀態(SELECT STATE);WR:更新狀態(UPDATE STATE);DEL:刪除狀態(DELETE STATE);INS:插入狀態(INSERT STATE)。 操作説明: INV:無效操作(INVALID);RD:讀操作(READ);WR:寫操作(WRITE);DEL:刪除操作(DEL);INS:插入操作(INSERT)。

詳細流程

SELECT具體流程如圖4-57所示。

在這裏插入圖片描述

圖4-57 SELECT時序圖

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

UPDATE具體流程如圖4 58所示。

在這裏插入圖片描述

圖4-58 UPDATE時序圖

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

DELETE具體流程如圖4-59所示。

在這裏插入圖片描述

圖4-59 DELETE時序圖

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

INSERT具體流程如圖4-60所示。

在這裏插入圖片描述

圖4-60 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用於保存行插入請求的數據。

4.3.6 併發控制

MOT採用源自SILO的單版本併發控制(concurrency control,CC)算法,是一種OCC算法。併發控制模塊滿足內存引擎的所有事務性需求,其主要設計目標是為MOT內存引擎提供各種隔離級別的支持。當前支持如下隔離級別。
(1) 讀已提交(READ-COMMITED)。
(2) 可重複讀(REPEATABLE-READ)。
在這裏插入圖片描述

圖4-61 MOT本地內存和全局內存

圖4-61顯示了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)事務流程
①在索引中搜索行引用。
② 將數據免鎖複製到基於類型的本地集,包括讀寫集(Read/Write Set, R/W set)。
③ 基於本地副本進行處理。
(2)校驗流程
① 按主鍵順序對寫集(Write Set)進行排序。
② 鎖定寫集中的所有行。
③ 驗證讀寫集的行。
④ 驗證本地行CSN是否更改。
⑤ 驗證該行是否為該鍵的最新版本(由於存在本地數據,可能並非最新)。
⑥ 驗證該行未被其他事務鎖定。
⑦ 如果以上任一項驗證失敗,則中止事務。
⑧ 否則將更新CSN後的所有寫集中的行復制回去,然後釋放這些行上的鎖。

2) 插入流程

(1)事務流程
① 構造一個CSN=0且狀態為不存在的新行r。
     添加r到寫集並視為常規更新。
     生成唯一的鍵k。
② 在狀態為不存在的情況下,向樹/索引添加從k → r的映射。

  • 如果k已經映射到一個狀態為存在的記錄,則插入失敗。
  • 否則在讀階段增大版本號。

(2)校驗流程
① 鎖定寫集。
② 驗證插入集(insert set)。
③ 若事務中止,則垃圾回收器記錄狀態為不存在的行。

3) 刪除流程

(1)事務流程
① 在索引中搜索行引用。
② 將行映射到本地緩存。
③ 將本地副本標記為已刪除。
(2)校驗流程
① 驗證行保持不變;已刪除的行將被視為更新。
② 從索引中刪除行,即將已刪除的哨兵/行放入垃圾回收器中。
在這裏插入圖片描述

圖4-62 MOT提交協議偽代碼 MOT提交協議偽代碼如圖4 62所示。

5. 關鍵類和數據結構

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

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

關鍵類和數據結構

描述

OccTransactionManager類

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

RowHeader類

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

4.3.7 重做日誌

MOT重做日誌(Redo Log)使用預寫式日誌(write-ahead logging,WAL)技術來確保數據完整性。WAL的核心概念是,內存中的數據和索引的更改只有在記錄下這些更改之後才會發生。因此寫入重做日誌是MOT提交協議的一部分。
如圖4-63所示,MOT存儲引擎的重做日誌模塊同樣使用openGauss磁盤引擎的日誌接口進行持久化和恢復。這意味着MOT重做數據被寫入相同的XLOG文件,並使用相同的XLOG邏輯。使用與openGauss磁盤引擎相同的日誌記錄接口可確保跨引擎事務的一致性,並減少複製、日誌恢復等模塊的宂餘實現。
在這裏插入圖片描述

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

1. 事務日誌記錄

與openGauss其他存儲引擎不同,MOT內存引擎僅在事務實際提交時才會寫入重做日誌。因此,在事務期間或事務中止時,數據不會寫入重做日誌。這樣可以減少寫入的數據量,從而減少不必要的磁盤IO調用,因為這種磁盤IO調用很慢。例如,如果在事務期間多次更新同一行,則只將表示已提交行的最終狀態寫入日誌。
由於設計MOT內存引擎時考慮了對接不同的數據庫的可能性,因此如圖4-64所示,MOT通過抽象的ILogger接口對接重做日誌。
在這裏插入圖片描述

圖4-64 ILogger接口

2. 日誌類型

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

圖4-65 RedoLogHandler接口

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

圖4-66 使用RedoLogHandler的事務日誌記錄

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

圖4-67 SynchronousRedoLogHandler

2) 同步組提交日誌記錄

同步組提交日誌由SegmentedGroupSyncRedoLogHandler類實現。它通過將幾個事務分組到一個寫塊(write block)中並一起寫入的方式優化日誌記錄。這種方法在一次調用中收集更多數據,可以最大限度地減少磁盤IO次數。除此之外, SegmentedGroupSyncRedoLogHandler將每個NUMA處理器(socket)的事務分組,以減少跨NUMA處理器的數據傳輸,因為跨NUMA處理器的數據訪問比同一NUMA處理器本地內存訪問慢。
當事務提交時,它將數據序列化到緩衝區中,這個緩衝區被傳輸到SegmentedGroupSyncRedoLogHandler,並放入一個提交組中。提交組(Commit Group)是一組序列化事務緩衝區的集合,這些事務緩衝區將被提交併寫入磁盤。根據不同的配置參數,當一個組被填滿或超過預先配置的時間時,MOT將關閉該組,並將該組內所有緩衝區一起寫入日誌。
圖4-68描述了將多個事務分組一起寫入的組提交邏輯。
在這裏插入圖片描述

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

3) 異步日誌

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

3. 關鍵類和數據結構

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

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

關鍵類和數據結構

描述

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處理。AddToLog為XLOGLogger子接口,XLOGLogger使用openGauss日誌基礎能力。因此,AddToLog是一個對openGauss XLOG接口的簡單委託

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

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ypeEh4Xa-1627887787292)(figures/zh-cn_image_0000001179630037.png)]

圖4-69 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以將線程委託給正確的處理程序。

4.3.8 檢查點

與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不需要停止數據庫就能實現物理一致性。虛擬一致性點是數據庫的視圖,它反映了在指定時間之前提交的所有修改,而不包含指定時間之後提交的修改,而且在不停止數據庫系統的情況下就可以獲得。實際上,可以通過部分多版本創建虛擬一致性點。
如圖4-70所示,精確部分多版本算法(precise partial multi-versioning)的總體思想如下。
(1) 每行都與兩個版本相關聯,一個是活動版本,一個是穩定版本。通常,穩定版本為空,表明穩定版本與活動版本一致,檢查點線程可以安全地記錄實時版本。穩定版本僅在檢查點的一個特定階段創建,此時檢查點線程將記錄該穩定版本。
(2) 每行維護一個穩定狀態位,指示穩定行的狀態。
在這裏插入圖片描述

圖4-70 檢查點概述

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

在這裏插入圖片描述

圖4-71 檢查點狀態機

通常,在進入下一階段之前,系統要等待所有上一階段開始提交的事務完成。
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) COMPLETE階段:這是緊跟捕獲階段完成的階段。檢查點捕獲完成後,系統進入COMPLETE階段。事務寫入行為恢復為與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還可將檢查點階段提前到COMPLETE。
(15) 通過創建map文件、結束文件等來完成檢查點,然後更新mot.ctrl文件。
(16) 等待CAPTURE階段開始的事務完成。
(17) 交換可用位和不可用位,以便將他們映射到穩定狀態位中的1和0值。
(18) 修改checkpoint階段為REST。這標誌着BEGIN_CHECKPOINT事件和MOT檢查點的結束。
(19) 然後openGauss將檢查點記錄插入到XLOG中,刷新到硬盤,最後更新控制文件。openGauss中的檢查點就此結束。

3. 關鍵類和數據結構

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

表4-40 檢查點的關鍵類和數據結構簡介

關鍵類和數據結構

描述

CheckpointManager類

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

CheckpointWorkerPool類

用於生成檢查點worker並刷盤

4.3.9 恢復

恢復部分有兩個目的,一是在崩潰或關機後達到最新的一致狀態,也稱為冷啟動(coldstart),二是在HA複製場景中,在備機側通過重放redo log完成複製。
冷啟動時,當所有WAL記錄都重放完成後恢復結束;但在HA複製場景中,複製將持續進行,直到備機改變狀態。
在恢復過程中,可能存在跨越多個重做日誌段的長事務,MOT將其保存在InProcessTransactions映射對象中,直到提交。包含在映射中的數據作為檢查點處理過程的一部分進行序列化,並在檢查點恢復期間進行反序列化。
此外,在最後恢復階段,完成所有檢查點/WAL記錄之後將設置最後的CSN,並將代理鍵生成器恢復到崩潰或關閉前的最新狀態。
為了恢復代理狀態,每個恢復線程(檢查點和重做日誌)都在更新每個線程id的代理最大鍵數組。最後,這些數組被合併成單個數組,用於恢復最後狀態。

1. 詳細流程

具體恢復流程如圖4-72所示。
在這裏插入圖片描述

圖4-72 恢復時序圖

恢復過程如下。
(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. 關鍵類和數據結構

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

表4-41 關鍵類和數據結構簡介

關鍵類和數據結構

描述

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操作的實際恢復

4.4 小結

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

第4章存儲引擎源碼解析的所有內容將告一段落,下一篇我們將開啟第5章事務機制源碼解析的內容介紹。敬請期待。