一、WAL日誌概述
pg中wal子系統的存在是爲了故障恢復,它也被用於基於時間點的恢復、基於日誌搬遷的Hot-standby複製。以下想描述一點wal日誌的設計理念。
wal日誌的一個基本假設是日誌條目必須先於它所描述的數據變化頁面持久化到穩定存儲(如硬盤)。這確保重放日誌到它的末端將使數據庫可以重新達到一致性狀態(不存在部分執行的事務)。爲了達到這一點,每一個數據頁面(堆或索引頁面)被標記上了影響本頁面的最新xlog記錄的LSN號。
LSN號,全稱Log sequence number, 實際上代表wal文件位置。
在wal重放時,可以覈對頁面的LSN來確定本條xlog記錄是否已經被重放(如果頁面LSN>日誌條目的位置,則該條日誌已被重放)。
二、事務日誌與WAL段文件
xlog詳細記錄了服務進程對數據庫的操作過程。xlog日誌文件在內存中按頁存放,每個頁面大小爲8kb,每個頁面都有一個頭部,頭部信息之後纔是xlog日誌記錄。
每個xlog文件都有一個ID,但事實上它被分成一個個固定大小(默認16MB,initdb時可由–wal-segsize指定)的XLOG段文件來存放。xlog文件號和段文件號可以用來唯一確定這個段文件。確定日誌文件內一個日誌記錄的地址時,只需用一個XLOG文件號和日誌記錄在該文件內的偏移量即可。
三、xlog文件初始化
initdb初始化data時,在函數bootstrap_template1中初始化template1模板庫時,通過popen調用postgres程序中的AuxiliaryProcessMain,AuxiliaryProcessMain中調用BootStrapXLOG函數完成XLOG段文件初始化工作。
四、xlog文件名
initdb生成xlog文件時,用以下宏生成文件名,
#define XLogFilePath(path, tli, logSegNo) \
snprintf(path, MAXPGPATH, XLOGDIR "/%08X%08X%08X", tli, \
(uint32) ((logSegNo) / XLogSegmentsPerXLogId), \
(uint32) ((logSegNo) % XLogSegmentsPerXLogId))
#define XLOG_SEG_SIZE (16 * 1024 * 1024)
#define XLogSegmentsPerXLogId (UINT64CONST(0x100000000) / XLOG_SEG_SIZE)
note: XLogSegmentsPerXlogId值爲256,XLOG_SEG_SIZE爲段大小16MB
宏中變量:
path: 代表文件名
tli:代表時間線,初始時間線爲1
logSegNo:代表段號,初始值爲1
以上值代入宏,經snprint格式化後,path值爲:
pg_xlog路徑+時間線+(uint32)段號/256+段號%256 --%08X代表按8位16進制數顯示
結果即:
pg_xlog/+00000001+00000000+00000001
Note: 此處,可以看出段文件名最後兩位最大值爲256,轉爲16進制則段文件名的最後8位最大爲000000FF.
五、xlog存儲結構
每一個XLOG文件有一個ID,事實上一個邏輯上的xlog文件物理上被分割爲一個個固定大小的段(默認16MB)來保存。xlog文件號和段號可以唯一確定這個段文件。確定日誌文件內的一個日誌記錄的地址時,只需用xlog文件號和日誌記錄在該文件內的偏移量即可。
對於每一個Xlog文件第一個段的第一個頁面是一個長頭部(Long Header),一個xlog文件頭部是不是長頭部,可以由其頭部XLogPageHeaderData的標誌位xlp_info確定出來,如果是長頭部,則:
XLogPageHeader page;
page->xlp_info = XLP_LONG_HEADER;
1)xlog日誌頁面頭部信息
xlog日誌文件分爲許多的邏輯段,每一個段文件又分成許多個頁面,每一個頁面大小爲一個塊的大小。對於每一個日誌頁面,需要在其頭部寫一個頭部信息XLogPageHeaderData,其結構如下:
/*
* Each page of XLOG file has a header like this:
*/
#define XLOG_PAGE_MAGIC 0xD093 /* can be used as WAL version indicator */
typedef struct XLogPageHeaderData
{
uint16 xlp_magic; /* magic value for correctness checks */
uint16 xlp_info; /* flag bits, see below */
TimeLineID xlp_tli; /* TimeLineID of first record on page */
XLogRecPtr xlp_pageaddr; /* XLOG address of this page */
/*
* When there is not enough space on current page for whole record, we
* continue on the next page. xlp_rem_len is the number of bytes
* remaining from a previous page.
*
* Note that xl_rem_len includes backup-block data; that is, it tracks
* xl_tot_len not xl_len in the initial header. Also note that the
* continuation data isn't necessarily aligned.
*/
uint32 xlp_rem_len; /* total len of remaining data for record */
} XLogPageHeaderData;
#define SizeOfXLogShortPHD MAXALIGN(sizeof(XLogPageHeaderData))
typedef XLogPageHeaderData *XLogPageHeader;
如果一個頁面是一個邏輯段文件的第一個頁面,那麼在頁面頭部信息標誌位會設置XLP_LONG_HEADER標記,那麼將在原頁面頭部信息的基礎上使用一個長的XLOG頁面頭部XLogLongPageHeaderData,其結構如下:
/*
* When the XLP_LONG_HEADER flag is set, we store additional fields in the
* page header. (This is ordinarily done just in the first page of an
* XLOG file.) The additional fields serve to identify the file accurately.
*/
typedef struct XLogLongPageHeaderData
{
XLogPageHeaderData std; /* standard header fields */
uint64 xlp_sysid; /* system identifier from pg_control */
uint32 xlp_seg_size; /* just as a cross-check */
uint32 xlp_xlog_blcksz; /* just as a cross-check */
} XLogLongPageHeaderData;
#define SizeOfXLogLongPHD MAXALIGN(sizeof(XLogLongPageHeaderData))
typedef XLogLongPageHeaderData *XLogLongPageHeader;
2)xlog日誌記錄結構信息
XLOG Record由兩部分組成,第一部分是XLOG Record的頭部信息,大小固定(24 Bytes),對應的結構體是XLogRecord;第二部分是XLOG Record data。
xlog記錄存儲格局:
Fixed-size header (XLogRecord struct)
XLogRecordBlockHeader struct
XLogRecordBlockHeader struct
…
XLogRecordDataHeader[Short|Long] struct
block data
block data
…
main data
XLOG Record按存儲的數據內容來劃分,大體可以分爲三類:
Record for backup block:存儲full-write-page的block,這種類型Record是爲了解決page部分寫的問題。在checkpoint完成後第一次修改數據page,在記錄此變更寫入事務日誌文件時整頁寫入(需設置參數full_page_write,默認爲打開);
Record for tuple data block:存儲page中的tuple變更,使用這種類型的Record記錄;
Record for Checkpoint:在checkpoint發生時,在事務日誌文件中記錄checkpoint信息。
XLogRecord記錄了Xlog記錄的相關控制信息,
typedef struct XLogRecord
{
uint32 xl_tot_len; /* total len of entire record */
TransactionId xl_xid; /* xact id */
XLogRecPtr xl_prev; /* ptr to previous record in log */
uint8 xl_info; /* flag bits, see below */
RmgrId xl_rmid; /* resource manager for this record */
/* 2 bytes of padding here, initialize to zero */
pg_crc32c xl_crc; /* CRC for this record */
/* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding */
} XLogRecord;
xl_tot_len //整條記錄總長度
xl_xid //事務ID
xl_prev //在日誌中的前一條記錄
xl_info //信息標誌位
xl_rmid //資源管理器ID
其中,xl_info被資源管理器使用,表示該日誌是哪種類型的日誌,其取值如下:
/* XLOG info values for XLOG rmgr */
#define XLOG_CHECKPOINT_SHUTDOWN 0x00
#define XLOG_CHECKPOINT_ONLINE 0x10
#define XLOG_NOOP 0x20
#define XLOG_NEXTOID 0x30
#define XLOG_SWITCH 0x40
#define XLOG_BACKUP_END 0x50
#define XLOG_PARAMETER_CHANGE 0x60
#define XLOG_RESTORE_POINT 0x70
#define XLOG_FPW_CHANGE 0x80
#define XLOG_END_OF_RECOVERY 0x90
#define XLOG_FPI_FOR_HINT 0xA0
#define XLOG_FPI 0xB0
其中,xl_rmid資源管理器的取值如下:
/* symbol name, textual name, redo, desc, identify, startup, cleanup */
PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, xlog_identify, NULL, NULL)
PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, xact_identify, NULL, NULL)
PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, smgr_identify, NULL, NULL)
PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, clog_identify, NULL, NULL)
PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, dbase_identify, NULL, NULL)
PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, tblspc_identify, NULL, NULL)
PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, multixact_identify, NULL, NULL)
PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, relmap_identify, NULL, NULL)
PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, standby_identify, NULL, NULL)
PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, heap2_identify, NULL, NULL)
PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, heap_identify, NULL, NULL)
PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, NULL, NULL)
PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL)
PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup)
PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup)
PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL)
PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup)
PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL)
PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL)
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL)
下列出其中較重要幾種資源管理器ID含義:
RM_XLOG_ID:該條日誌記錄的是一個檢查點信息
RM_XACT_ID:該條日誌記錄的是一個事務的提交或終止信息
RM_CLOG_ID:該條日誌記錄的是CLOG中某一頁的初始化
RM_HEAP_ID:該條日誌記錄的是對堆中元組進行修改的信息
XLOG Record data
XLOG Record data是存儲實際數據的地方,由以下幾部分組成:
0…N個XLogRecordBlockHeader,每一個XLogRecordBlockHeader對應一個block data;
XLogRecordDataHeader[Short|Long],如數據大小<256 Bytes,則使用Short格式,否則使用Long格式;
block data:full-write-page data和tuple data。對於full-write-page data,如啓用了壓縮,則數據壓縮存儲,壓縮後該page相關的元數據存儲在XLogRecordBlockCompressHeader中;
main data: checkpoint等日誌數據.
。。。
細節內容有點多,本篇先寫到這裏