天天看點

PgSQL · 特性分析 · 資料庫崩潰恢複(下)

在上期月報PgSQL · 特性分析 · 資料庫崩潰恢複(上),我們分析了PostgreSQL在資料庫非正常退出後(包括通過recovery.conf使用者主動恢複)的處理,概括起來分為以下幾步:

1.如果滿足以下條件之一,則進行非正常退出恢複

pg_control檔案中的資料庫狀态不正常(非DB_SHUTDOWNED)

pg_control檔案中記錄的最新檢查點讀取不到XLOG日志檔案

2.使用者指定recovery.conf檔案主動恢複

3.根據pg_control、backup_label确定恢複的XLOG日志記錄起點

不斷讀取每個XLOG record,根據所屬的資料總管去調用各自:

rm_startup()

rm_redo(EndRecPtr, record)

rm_cleanup();

4.不斷恢複XLOG日志記錄,直到滿足以下條件之一,則停止恢複正常啟動

達到recovery.conf檔案中規定的最終的目标恢複位點

目前XLOG日志全部應用完成

5.清理環境,并啟動需要的輔助程序

其中,PostgreSQL會根據每個XLOG record所屬的資料總管操作來執行對應的函數。PostgreSQL中有以下的資料總管:

而每種資料總管,都存在幾種具體的操作,例如RM_HEAP_ID包括以下操作(後面的數字代表對應XLogRecord中xl_info字段高4位,詳情參考上期月報):

下面我們将以堆表的INSERT操作為例,具體分析對應的XLOG record内容和資料總管對應的處理函數。

**XLOG record

XLOG record 結構**

在PgSQL · 特性分析 · 資料庫崩潰恢複(上)中,分析了WAL日志頁的基本結構。其中,一條日志記錄的的組織形式如下所示(以下分析基于RDS for PostgreSQL,即9.4版本):

為了更好地探究堆表INSERT操作對應XLOG record 的内容,我們建立一個簡單的TABLE,并執行INSERT操作:

XLogInsert函數

在執行INSERT操作的時候,PostgreSQL會調用heap_insert函數,其中會調用XLogInsert去插入對應的XLOG record:

注意:實際上是調用兩次XLogInsert,除了HEAP INSERT操作的XLOG record,還有事務送出的XLOG record。

函數XLogInsert的傳回值是XLogRecPtr結構類型,即LSN(log secquence number)。heap_insert函數在執行XLogInsert()後,把其傳回值XLogRecPtr記錄指派給對應的page的PageHeaderData結構中,以實作WAL機制(參考PgSQL · 特性分析 · Write-Ahead Logging機制淺析)。

XLogInsert函數中會去包裝一個XLOG record,并把它刷寫到磁盤,我們接下來分析一下XLogInsert函數。

XLogInsert函數定義:

XLogInsert的三個函數參數分别是:

rmid

1.RmgrId類型

2.代表本條XLOG record所屬的資料總管類型,例如我們上面的例子中INSERT操作屬于RM_HEAP_ID,即堆表資料總管

info

1.uint8類型

2.代表資料總管對應的操作,例如堆表中INSERT操作為0x00

rdata

1.XLogRecData指針類型(連結清單)

2.每個XLogRecData結構體存儲對應的資料總管資料rmgr-specific data

之是以要用XLogRecData鍊,是因為在所要處理的日志記錄實體資料在記憶體空間可能不是連續存儲的,而且資料可能分布在多個緩沖區内,需要用XlogRecData連結清單将它們組織起來。XlogRecData資料結構如下:

其中,buffer_std該值為true,則容許XLOG釋放備份頁的空閑空間,空閑空間由pd_lower和pd_upper限定:

pd_lower表示頁面起始位置與未配置設定空間開頭的位元組偏移

pd_upper表示頁面末尾位置與未配置設定空間末尾的位元組偏移

通過分析三個XLogInsert函數參數,可以看出XLogInsert主要是将rdata封裝成一個XLOG record。接下來我們将分析heap_insert函數内如何對rdata進行指派。

heap_insert函數主要操作HeapTupleData結構體,對應在每個資料頁中存儲的每個tuple,結構如下圖所示:

PgSQL · 特性分析 · 資料庫崩潰恢複(下)

tuple分為頭部資訊和資料資訊,這裡不再展開,我們将在分析PostgreSQL的MVCC機制時,将其中的結構詳細分析。

heap_insert函數的主要操作如下:

調用RelationGetBufferForTuple方法找到shmem裡緩存資料塊buffer

調用RelationPutHeapTuple方法,把組裝好的元組tuple放到對應buffer中合适的位置

指派XLogRecData類型變量rdata,通過代碼分析可以看出rdata實際上是對tuple的内容摘要

XLogRecData rdata[4]; 堆表的INSERT操作有4個XLogRecData結構體組成的連結清單

rdata[0].data 存儲一個xl_heap_insert結構,用于标示一些基本資訊:

rdata[0].buffer = InvalidBuffer

rdata[1].data存儲一個xl_heap_header結構,存儲tuple頭部的簡化資訊:

rdata[1].buffer = need_tuple_data ? InvalidBuffer : buffer;如果需要存儲整個資料塊,則把buffer指派給rdata

rdata[2].data存儲tuple頭部後面的資料,比如INSERT操作的插入元組的每列的數值

rdata[2].buffer = need_tuple_data ? InvalidBuffer : buffer;同rdata[1]

如果需要存儲整個資料塊,則rdata[3].buffer=buffer

調用XLogInsert,将rdata封裝成XLOG record寫入WAL緩沖區,如果需要切換日志段檔案,調用XLogWrite刷寫到磁盤

經過以上分析,我們可以知道,XLOG record的核心部分是資料總管資料(XLogRecData)和備份資料塊(backup block data),這兩個資料包含了我們恢複時候需要的資料。在各個資料總管的具體操作調用XLogInsert之前,需要對這兩個部分進行填充。

在恢複過程中,每個資料總管對應的處理函數是不同的,為了更好的抽象資料總管,在PostgreSQL中定義了RmgrData結構體和一個RmgrData類型數組RmgrTable。

RmgrTable的下标對應上文提到的資料總管ID,比如HEAP表對應的RmgrData是RmgrTable[RM_HEAP_ID]。

RmgrData中定義了rm_redo、rm_desc、rm_startup、rm_cleanup四個函數指針,分别對應每個資料總管具體的redo、desc、startup、cleanup函數,我們主要分析下其中的rm_redo恢複函數

不同資料總管的REDO函數參數都為(XLogRecPtr lsn, XLogRecord *record),這兩個類型的參數我們在前面兩期月報中均有涉及。其中:

XLogRecPtr類型代表着該log record在日志序列中的位置

XLogRecord類型代表着該log record的頭部資訊

REDO函數主要是根據該log record的位置取到對應的log record進行相應的恢複操作,下面我們以堆表INSERT操作為例,分析下對應的REDO函數heap_xlog_insert:

調用XLogRecGetData(record)方法,取出上文中的xl_heap_insert結構xlrec

如果該XLOG record存在full-page image,則恢複該資料塊

将上文提到的rdata恢複成tuple,寫入到緩存資料塊中

标記緩存資料塊為髒頁,等待刷出

通過以上分析,我們可以大體知道XLOG record滿足了以下幾點才能實作崩潰恢複甚至是任意時間點恢複:

可靠性

1.full-page image,每次checkpoint第一次更新時都需要将整頁複制

2.CRC32校驗碼

3.插入XLOG record時不接受其他信号

可重複性

1.多次重放一個log record,得到的結果相同

可恢複性

1.大多數資料總管操作對應的log record中存儲的都是對應資料在磁盤存儲的一個摘要(例如INSERT操作的rdata是tuple的一個摘要)

XLOG日志在PostgreSQL運維中占據了非常重要的地位,從WAL機制到備份恢複以及主備複制,許多功能都離不開XLOG日志。推薦大家看下《PostgreSQL Replication》這本書,加深對PostgreSQL中XLOG以及Replication的了解。

另外,PostgreSQL XLOG目前也存在一些問題,最明顯的問題就是寫放大,因為full-page image導緻XLOG record體積太大,如果設定不合理,可能日志的體積是資料體積的20倍左右。但是經過參數調優,可以盡可能地避免這種情況,可參考文章如何遏制PostgreSQL WAL的瘋狂增長。