postgresql源码学习(二十)—— 事务日志①-日志格式

67 篇文章 51 订阅
34 篇文章 3 订阅

关于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位无符号整数,可用值为 00xFFFFFFFE。

typedef uint32 BlockNumber;
#define InvalidBlockNumber		((BlockNumber) 0xFFFFFFFF)
#define MaxBlockNumber			((BlockNumber) 0xFFFFFFFE)

从图中可以看到,XLogRecordBlockHeader可能包括几个可选项:

  • XLogRecordBlockImageHeader:包含full page image(全页镜像,也叫备份区块,用于全页写),后面会提到
  • XLogRecordBlockCompressHeader:启用压缩
  • RelFileNoderelfilenode.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 Checkpointcheckpoint发生时,在事务日志文件中记录checkpoint信息(其中包括Redo point).

每种类型具体包含的头和数据信息都不完全相同,可以结合前面结构体介绍去看。

https://img-blog.csdnimg.cn/20190710154417292.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9kYXp1aWJhMDA4LmJsb2cuY3Nkbi5uZXQ=,size_16,color_FFFFFF,t_70

      在之前的文章 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博客

​​​​​​​Postgresql Wal 日志构建原理 | 学习笔记

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值