关于WAL日志的一些基础知识,可以参考之前的文章,本篇侧重于源码部分。
pg 崩溃恢复篇(一)—— WAL的作用与全页写机制_Hehuyi_In的博客-CSDN博客_pg wal pg 崩溃恢复篇(二)—— WAL文件结构及管理_Hehuyi_In的博客-CSDN博客_wal文件 ?
一、 日志组成结构
来看这个图
层次比较多,具体来看
- 每个WAL文件(日志段)大小为16M,它在内部划分为多个页面,每个页大小为8K(这也是pg需要全页写的原因)
- 每个日志页由页头信息(Header)+日志记录(Record)组成
- Header分为两类:
- XLogLongPageHeaderData:日志段第一个页的Header信息(每个段只有一个,图中深蓝色部分),存放日志段的长度、段页面大小等信息
- XLogPageHeaderData:日志段其他页的Header信息(除第一个页外每个日志页都有一个,图中浅蓝色部分),存放事务日志对应的版本、时间线等信息
- XLogLongPageHeaderData包含XLogPageHeaderData及一些额外信息
- 每条日志记录又由XLogRecord结构体+数据(XLOG Record data)组成,它是事务日志的最小单元,每个日志记录都表示修改数据库的一个动作
- 日志数据又可以再分为:块头(XLogRecordBlockHeader)+日志头(XLogRecordDataHeader)+块数据(Block Data)+主数据(Main Data)
二、 日志页头信息
以下按照代码中出现的先后顺序排列
1. 通用页头信息 XLogPageHeaderData
? ? ? ?日志段其他页的Header信息(除第一个页外每个日志页都有一个,图中浅蓝色部分),存放事务日志对应的版本、时间线等信息。
/*
* Each page of XLOG file has a header like this:
*/
#define XLOG_PAGE_MAGIC 0xD10D /* 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,页面中第一条记录的时间线id */
XLogRecPtr xlp_pageaddr; /* XLOG address of this page,本日志页的首地址 */
/*
* 当页面剩余空间不足以保存整条记录时,需要保存到下一个日志页,xlp_rem_len就用来记录剩余需要保存的记录长度,它跟踪初始页头的xl_tot_len长度(xlp_rem_len is the number of bytes remaining from a previous page; it tracks xl_tot_len in the initial header.)
*/
uint32 xlp_rem_len; /* total len of remaining data for record,当前页面续接前一页面的日志长度 */
} XLogPageHeaderData;
// XLogPageHeaderData的大小
#define SizeOfXLogShortPHD MAXALIGN(sizeof(XLogPageHeaderData))
// 定义XLogPageHeaderData对应指针
typedef XLogPageHeaderData *XLogPageHeader;
跨页访问类似于
2. 首页头信息 XLogLongPageHeaderData
- XLogLongPageHeaderData:日志段第一个页的Header信息(每个段只有一个,图中深蓝色部分),存放日志段的长度、段页面大小等信息
- XLogLongPageHeaderData包含XLogPageHeaderData及一些额外信息
/*
* 当设置了XLP_LONG_HEADER标记位时(只在每个WAL日志段的第一个页会设),我们还要在页头存储一些额外信息,这些额外信息由于精确定位文件
*/
typedef struct XLogLongPageHeaderData
{
XLogPageHeaderData std; /* standard header fields,标准页头信息 */
uint64 xlp_sysid; /* system identifier from pg_control,来自控制文件的系统id */
uint32 xlp_seg_size; /* just as a cross-check,日志段大小,用于检查 */
uint32 xlp_xlog_blcksz; /* just as a cross-check,日志页大小,用于检查 */
} XLogLongPageHeaderData;
// XLogLongPageHeaderData的大小
#define SizeOfXLogLongPHD MAXALIGN(sizeof(XLogLongPageHeaderData))
// 定义XLogLongPageHeaderData对应指针
typedef XLogLongPageHeaderData *XLogLongPageHeader;
3. 一些宏定义
/* When record crosses page boundary, set this flag in new page's header,当日志记录跨页时,设置该标记 */
#define XLP_FIRST_IS_CONTRECORD 0x0001
/* This flag indicates a "long" page header,是long header信息(即XLogLongPageHeaderData) */
#define XLP_LONG_HEADER 0x0002
/* This flag indicates backup blocks starting in this page are optional,在pg_start_backup函数开始后,数据库会进入FPW状态,当备份停止时,在WAL日志上打上XLP_BKP_REMOVABLE标记。从这里开始FPW不是必须执行,进入可选状态 */
#define XLP_BKP_REMOVABLE 0x0004
/* All defined flag bits in xlp_info (used for validity checking of header),前面提到的flag标记位,用于检查header有效性 */
#define XLP_ALL_FLAGS 0x0007
//判断页类型,看是用long页大小还是标准页大小
#define XLogPageHeaderSize(hdr) \
(((hdr)->xlp_info & XLP_LONG_HEADER) ? SizeOfXLogLongPHD : SizeOfXLogShortPHD)
/* wal_segment_size can range from 1MB to 1GB,日志段最小及最大大小 */
#define WalSegMinSize 1024 * 1024
#define WalSegMaxSize 1024 * 1024 * 1024
再来看日志记录部分
内容也比较多,我们按图中的层次分别介绍:
- 日志记录通用头XLogRecord
- 日志记录头信息:日志记录块头XLogRecordBlockHeader+日志记录数据头XLogRecordDataHeader
- 日志记录数据:块数据Block Data+主数据Main Data。
?
三、 日志记录通用头 XLogRecord
typedef struct XLogRecord
{
uint32 xl_tot_len; /* total len of entire record,记录总长度 */
TransactionId xl_xid; /* xact id,事务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,该记录的CRC(循环冗余校验) */
/* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding,后面是另两个Header结构体 */
} XLogRecord;
xl_info记录标记位和产生这个记录的动作:
- 其中低4位存储两种标记信息:XLR_SPECIAL_REL_UPDATE和XLR_CHECK_CONSISTENCY,由XLogInsert函数的调用者传入
/*
* 如果WAL记录使用特殊的方式(不涉及通常块引用)更新了关系的存储文件,设置此标记。PostgreSQL本身并不使用这种方法,但它允许外部工具读取WAL并跟踪修改后的块,以识别这种特殊的记录类型。
*/
#define XLR_SPECIAL_REL_UPDATE 0x01
/*
* 在恢复时强制执行一致性检查。如过启用,会执行全页写操作,并在恢复时用它进行一致性检查。在需要时,XLogInsert的调用者可设置此标记,但如果rmgr启用了wal_consistency_checking,则会无条件执行一致性检查。
*/
#define XLR_CHECK_CONSISTENCY 0x02
- 高4位表示产生这个记录的动作(最多16种),不同的resource id下动作信息不同,因此每种resource id对应的动作数量会受限制。以Heap操作为例,其resource id是RM_HEAP_ID
/*
* XLOG allows to store some information in high 4 bits of log
* record xl_info field. We use 3 for opcode and one for init bit.
*/
#define XLOG_HEAP_INSERT 0x00
#define XLOG_HEAP_DELETE 0x10
#define XLOG_HEAP_UPDATE 0x20
#define XLOG_HEAP_TRUNCATE 0x30
#define XLOG_HEAP_HOT_UPDATE 0x40
#define XLOG_HEAP_CONFIRM 0x50
#define XLOG_HEAP_LOCK 0x60
#define XLOG_HEAP_INPLACE 0x70
#define XLOG_HEAP_OPMASK 0x70
/*
* When we insert 1st item on new page in INSERT, UPDATE, HOT_UPDATE,
* or MULTI_INSERT, we can (and we do) restore entire page in redo。在日志页写入第一条信息时进行标记,用于全页写
*/
#define XLOG_HEAP_INIT_PAGE 0x80
四、 日志记录块头
1. XLogRecordBlockHeader
/*
* Header info for block data appended to an XLOG record.日志记录中块数据的头信息
*/
typedef struct XLogRecordBlockHeader
{
uint8 id; /* block reference ID,块引用id */
uint8 fork_flags; /* fork within the relation, and flags,表中的分支以及标记位 */
uint16 data_length; /* number of payload bytes (not including page image),荷载字节数,不包括页面镜像和XLogRecordBlockHeader结构体本身 */
/* If BKPBLOCK_HAS_IMAGE, an XLogRecordBlockImageHeader struct follows,如果设置了BKPBLOCK_HAS_IMAGE,还会包含XLogRecordBlockImageHeader结构体 */
/* If BKPBLOCK_SAME_REL is not set, a RelFileNode follows,如果没有设置BKPBLOCK_SAME_REL,则会包含RelFileNode */
/* BlockNumber follows,后续为块号 */
} XLogRecordBlockHeader;
#define SizeOfXLogRecordBlockHeader (offsetof(XLogRecordBlockHeader, data_length) + sizeof(uint16))
BlockNumber的定义在block.h文件,是一个32位无符号整数,可用值为 0到0xFFFFFFFE。
typedef uint32 BlockNumber;
#define InvalidBlockNumber ((BlockNumber) 0xFFFFFFFF)
#define MaxBlockNumber ((BlockNumber) 0xFFFFFFFE)
从图中可以看到,XLogRecordBlockHeader可能包括几个可选项:
- XLogRecordBlockImageHeader:包含full page image(全页镜像,也叫备份区块,用于全页写),后面会提到
- XLogRecordBlockCompressHeader:启用压缩
- RelFileNode(relfilenode.h):如果没有设置BKPBLOCK_SAME_REL
2. XLogRecordBlockImageHeader
当包含full-page image(备份区块,即设置了BKPBLOCK_HAS_IMAGE)时,附加的头信息。
/*
* Additional header information when a full-page image is included
* (i.e. when BKPBLOCK_HAS_IMAGE is set). 当包含full-page image(备份区块,即设置了BKPBLOCK_HAS_IMAGE)时,附加的头信息
*
* XLOG代码知道PG数据页通常在中间包含一些未使用的 hole(孔、洞,即空闲空间),大小为零字节。既然我们知道hole都是零,因此可以从存储的数据中删除它(而且它也没有被计入XLOG记录的CRC中)。 因此,实际的块数据量为 BLCKSZ - hole的大小。
*
* 另外,在启用wal_compression时,会在去掉hole后,尝试使用PGLZ压缩算法压缩full page image。这可以减小WAL容量,但会增加额外的CPU消耗。
* 在这种情况下,由于hole的长度不能通过从BLCKSZ中减去page image字节数来计算,所以它基本上需要作为额外的信息来存储。但如果hole不存在,我们可以假设hole的大小为0,不需要存储额外的信息。
* 请注意,如果压缩节省的字节数小于额外信息的长度,那么在WAL中存储page image的原始版本,而不是压缩后的版本。
* 因此,当page image被成功压缩时,实际的块数据量小于BLCKSZ-hole的大小-额外信息的大小。
*/
typedef struct XLogRecordBlockImageHeader
{
uint16 length; /* number of page image bytes,页面镜像字节数 */
uint16 hole_offset; /* number of bytes before "hole",hole前面的字节数 */
uint8 bimg_info; /* flag bits, see below,标记位 */
/*
* If BKPIMAGE_HAS_HOLE and BKPIMAGE_IS_COMPRESSED, an
* XLogRecordBlockCompressHeader struct follows.
*/
} XLogRecordBlockImageHeader;
/* Information stored in bimg_info */
#define BKPIMAGE_HAS_HOLE 0x01 /* page image has "hole" */
#define BKPIMAGE_IS_COMPRESSED 0x02 /* page image is compressed */
#define BKPIMAGE_APPLY 0x04 /* page image should be restored during replay */
3. XLogRecordBlockCompressHeader
/*
* Extra header information used when page image has "hole" and
* is compressed.
*/
typedef struct XLogRecordBlockCompressHeader
{
uint16 hole_length; /* number of bytes in "hole" */
} XLogRecordBlockCompressHeader;
#define SizeOfXLogRecordBlockCompressHeader \
sizeof(XLogRecordBlockCompressHeader)
4. RelFileNode
这个结构体很简单
typedef struct RelFileNode
{
Oid spcNode; /* tablespace */
Oid dbNode; /* database */
Oid relNode; /* relation */
} RelFileNode;
5. MaxSizeOfXLogRecordBlockHeader
XLogRecordBlockHeader最大size,最大就是每部分都有,然后加起来。
/*
* Maximum size of the header for a block reference. This is used to size a
* temporary buffer for constructing the header.
*/
#define MaxSizeOfXLogRecordBlockHeader \
(SizeOfXLogRecordBlockHeader + \
SizeOfXLogRecordBlockImageHeader + \
SizeOfXLogRecordBlockCompressHeader + \
sizeof(RelFileNode) + \
sizeof(BlockNumber))
五、 日志记录数据头 XLogRecordDataHeaderShort/Long
? ? ? ?main data部分的头信息,分为长短两种。如果数据长度小于256 bytes则用短的,并用一个字节保存长度,否则用长的。
/*
* These structs are currently not used in the code, they are here just for
* documentation purposes. 这些结构体现在已经没有在代码中使用了,还保留在这里只是为了文档记录的目的。
*/
typedef struct XLogRecordDataHeaderShort
{
uint8 id; /* XLR_BLOCK_ID_DATA_SHORT */
uint8 data_length; /* number of payload bytes */
} XLogRecordDataHeaderShort;
#define SizeOfXLogRecordDataHeaderShort (sizeof(uint8) * 2)
typedef struct XLogRecordDataHeaderLong
{
uint8 id; /* XLR_BLOCK_ID_DATA_LONG */
/* followed by uint32 data_length, unaligned */
} XLogRecordDataHeaderLong;
#define SizeOfXLogRecordDataHeaderLong (sizeof(uint8) + sizeof(uint32))
六、 日志记录真正的数据部分
? ? ? ? 这里我们合并介绍块数据 Block Data和主数据 Main Data,因为它们是相关的。
XLOG Record按存储的数据内容来划分,大体可以分为三类:
- Record for backup block(备份区块):存储full-write-page的block,是为了解决日志页部分写的问题;
- Record for tuple data block(非备份区块):在full-write-page后,记录相应的page中的tuple变更
- Record for Checkpoint:checkpoint发生时,在事务日志文件中记录checkpoint信息(其中包括Redo point).
每种类型具体包含的头和数据信息都不完全相同,可以结合前面结构体介绍去看。
? ? ? 在之前的文章 pg 崩溃恢复篇(三)—— 走近XLOG记录_Hehuyi_In的博客-CSDN博客 也有记录过,可以参考。
参考
《PostgreSQL技术内幕:事务处理深度探索》第4章
PostgreSQL DBA(17) - XLOG Record data内部结构 - 简书
PostgreSQL 源码解读(109)- WAL#5(相关数据结构) - 简书
PostgreSQL xlog格式 backup full page_yzs87的博客-CSDN博客
PostgreSQL xlog格式之checkpoint_yzs87的博客-CSDN博客
PostgreSQL xlog格式之no backup full page_ITPUB博客
|