一、 XLOG记录的内部结构
XLOG记录由通用头部分XLogRecord+数据部分组成
而数据部分又有自己的头部分和数据部分
1. XLOG记录通用头部
所有XLOG记录都有一个的通用头部分,由结构体XLogRecord定义。9.5及之后定义如下
typedef struct XLogRecord
{
uint32 xl_tot_len; /* 记录总长度 */
TransactionId xl_xid; /* txid */
XLogRecPtr xl_prev; /* 指向日志中前一条记录的指针 */
uint8 xl_info; /* 标记位 */
RmgrId xl_rmid; /* 本记录的资源管理器 */
/* 这里有2 Bytes的填充,初始化为0 */
pg_crc32c xl_crc; /* 本记录的CDC */
/* 随后是XLogRecordBlockHeaders与XLogRecordDataHeader,不带填充 */
} XLogRecord;
- 9.5之前XLogRecord结构在src/include/access/xlog.h中定义
- 9.5开始在XLogRecord结构在src/include/access/xlogrecord.h中定义
- heap_xlog_insert和heap_xlog_update在src/backend/access/heap/heapam.c中定义
- 函数xact_redo_commit在src/backend/access/transam/xact.c中定义
2. XLOG记录的数据部分(9.5及之后)
在9.5之前,XLOG记录没有通用的格式,因此每种资源管理器都需要定义各自的格式。在这种情况下,维护源代码和实现与WAL相关的新功能变得越来越困难。为了解决这个问题,9.5版本中引入了一种不依赖于资源管理器的通用的结构化格式。
XLOG记录的数据部分可以分为两部分 —— 头部分与数据部分
头部分包含零或多个XLogRecordBlockHeaders,以及零或一个XLogRecordDataHeaderShort(或XLogRecordDataHeaderLong),但必须至少包含其中一个。
当记录存储整页(即备份块)时,XLogRecordBlockHeader包括XLogRecordBlockImageHeader;如果启用压缩,还会包括XLogRecordBlockCompressHeader。
XLogRecordBlockHeaders定义如下:
XLogRecordDataHeaderShort/Long 定义如下:
如果数据长度小于256B,则使用短版本格式,即使用单个字节来保存长度数值,否则使用长版本格式
XLogRecordBlockImageHeader定义如下:
XLogRecordBlockCompressHeader定义如下:
数据部分由零个或多个块数据和零个或一个主数据组成,它们分别对应于XLogRecordBlockHeader和XLogRecordDataHeader。
WAL压缩
在9.5或更高版本中,通过设置参数wal_compression = enable,可以使用LZ压缩方法压缩XLOG记录中的整个页。在这种情况下,将添加结构体XLogRecordBlockCompressHeader。
该特征有两个优点和一个缺点。优点是降低了写入记录的I / O成本并抑制了WAL段文件的消耗。缺点是需要消耗大量CPU资源才能进行压缩。
二、 备份块与非备份块
1. 备份块
备份块如上图a所示,它由四个数据结构体和一个数据对象组成:
- 结构体XLogRecord(通用头部分)
- 数据部分的头数据XLogRecordBlockHeader,且包含一个LogRecordBlockImageHeader
- 数据部分的头数据XLogRecordDataHeaderShort
- 备份块(块数据)
- 结构体 xl_heap_insert(主数据)
XLogRecordBlockHeader包含用于在数据库集群中定位区块的变量(relfilenode,fork编号和区块号)
XLogRecordImageHeader包含此块的长度和偏移号
XLogRecordDataHeaderShort存储xl_heap_insert结构的长度,该结构是记录的主要数据。
除了在某些特殊情况下(如逻辑解码和推测性插入),一般不使用包含整页镜像的XLOG记录的主数据。它们会在记录重放时被忽略,属于冗余数据,未来可能会对其进行改进。此外,备份块记录的主数据取决于创建它们的语句。例如,UPDATE语句会追加写入xl_heap_lock或xl_heap_updated。
2. 非备份块
备份块如上图b所示,它由四个数据结构和一个数据对象组成:
- 结构体XLogRecord(通用头部分)
- 数据部分的头数据XLogRecordBlockHeader
- 数据部分的头数据XLogRecordDataHeaderShort
- 一条被插入的元组(严格地说,是一个xl_heap_header结构与完整的插入数据)
- 结构体 xl_heap_insert(主数据)
XLogRecordBlockHeader包含用于在数据库集群中定位区块的变量(relfilenode,fork编号和区块号),用于指定该元组被插入哪个块中,以及要插入数据部分的长度。
XLogRecordDataHeaderShort存储xl_heap_insert结构的长度,该结构是记录的主要数据。
新的xl_heap_insert只包含两个值:当前元组在块内的偏移量,以及可见性标志。该结构非常简单,因为XLogRecordBlockHeader存储了旧版本中绝大部分数据。
xl_head_insert定义如下:
3. 检查点记录
检查点记录如图c所示,它由三个数据结构组成:
- 结构体XLogRecord(通用头部分)
- 数据部分的头数据XLogRecordDataHeaderShort,包含主数据长度
- 结构体CheckPoint(主要数据)
结构xl_heap_header在src/include/access/htup.h中定义,CheckPoint结构在src/include/catalog/pg_control.h中定义。
三、 XLOG记录写入
现在我们已经准备好了理解XLOG记录的写入过程,以下列语句执行为例,探索PostgreSQL内幕。
testdb=# INSERT INTO tbl VALUES ('A');
上述语句会调用内部函数exec_simple_query(),其伪代码如下:
exec_simple_query() @postgres.c
(1) ExtendCLOG() @clog.c /* 将当前事务的状态"IN_PROGRESS" 写入CLOG */
(2) heap_insert()@heapam.c /* 插入元组,创建一条xlog记录并调用函数XLogInsert */
(3) XLogInsert() @xlog.c /* 9.5开始为xloginsert.c,将插入元组的xlog记录写入WAL缓冲区,更新页面的pd_lsn */
(4) finish_xact_command() @postgres.c /* 执行提交*/
XLogInsert() @xlog.c /* 9.5开始为xloginsert.c,将该提交行为的xlog记录写入WAL缓冲区 */
(5) XLogWrite() @xlog.c /* 将WAL缓冲区中所有的xlog写入WAL文件 */
(6) TransactionIdCommitTree() @transam.c /* 在CLOG中将事务状态由"IN_PROGRESS"改为"COMMITTED" on the CLOG */
XLOG记录的写入过程(9.4版本),如下图:
四、 WAL写进程
WAL写进程是一个后台进程,用于定期检查WAL缓冲区,并将所有未写入的XLOG记录写入WAL文件。此进程的目的是避免XLOG记录的突发写入。若未启用该进程,在大量提交数据时,XLOG记录的写入可能会成为瓶颈。
WAL写进程默认是启用的,并且无法禁用。但检查间隔可以通过参数wal_writer_delay配置,默认值为200毫秒。
参考
The Internals of PostgreSQL : Chapter 9 Write Ahead Logging — WAL