摘要
事务日志是数据库的重要组成部分, 存储了数据库系统中所有更改和操作的历史, 以确保数据库不会因为故障 (例如掉电或其他导致服务器崩溃的故障) 而丢失数据. 在 PostgreSQL(以下简称 PG)中, 事务日志文件称为 Write Ahead Log(以下简称 WAL).
本文对 PG 中事务日志文件的结构进行了简要的剖析, 内容包括 WAL 基本术语, WAL 文件组成, WAL segment file 内部结构和内容剖析以及 pg_waldump 工具简介.
一, WAL 基本术语
为了更好的理解 WAL 和便于沟通, 有必要首先对相关的 WAL 术语进行简要的介绍.
1, REDO log
Redo log 通常称为重做日志, 在写入数据文件前, 每个变更都会先行写入到 Redo log 中. 其用途和意义在于存储数据库的所有修改历史, 用于数据库故障恢复 (Recovery), 增量备份(Incremental Backup),PITR(Point In Time Recovery) 和复制(Replication).
2, WAL segment file
为了便于管理, PG 把事务日志文件划分为 N 个 segment, 每个 segment 称为 WAL segment file, 每个 WAL segment file 大小默认为 16MB.
3, XLOG Record
这是一个逻辑概念, 可以理解为 PG 中的每一个变更都对应一条 XLOG Record, 这些 XLOG Record 存储在 WAL segment file 中. PG 读取这些 XLOG Record 进行故障恢复 / PITR 等操作.
4, WAL buffer
WA 缓冲区, 不管是 WAL segment file 的 header 还是 XLOG Record 都会先行写入到 WAL 缓冲区中, 在 "合适的时候" 再通过 WAL writer 写入到 WAL segment file 中.
5, LSN
LSN 即日志序列号 Log Sequence Number. 表示 XLOG record 记录写入到事务日志中位置. LSN 的值为无符号 64 位整型(uint64). 在事务日志中, LSN 单调递增且唯一.
6, checkpointer
checkpointer 是 PG 中的一个后台进程, 该进程周期性地执行 checkpoint. 当执行 checkpoint 时, 该进程会把包含 checkpoint 信息的 XLOG Record 写入到当前的 WAL segment file 中, 该 XLOG Record 记录包含了最新 Redo pint 的位置.
7, checkpoint
检查点 checkpoint 由 checkpointer 进程执行, 主要的处理流程如下:
(1) 获取 Redo point, 构造包含此 Redo point 检查点 (详细请参考 Checkpoint 结构体) 信息的 XLOG Record 并写入到 WAL segment file 中;
(2) 刷新 Dirty Page 到磁盘上;
(3) 更新 Redo point 等信息到 pg_control 文件中.
8, REDO point
REDO point 是 PG 启动恢复的起始点, 是最后一次 checkpoint 启动时事务日志文件的末尾亦即写入 Checkpoint XLOG Record 时的位置(这里的位置可以理解为事务日志文件中偏移量).
9, pg_control
pg_control 是磁盘上的物理文件, 保存检查点的基本信息, 在数据库恢复中使用, 可通过命令 pg_controldata 查看该文件中的内容.
二, WAL 文件组成
如前所述, 事务日志存储了数据库系统中所有更改和操作的历史, 随着数据库的运行, 事务日志大小不断的增长, 那么事务日志有大小限制吗? 在 PG 中, 答案是肯定的: 大小有限制.
PG 使用无符号 64bit 整型 (uint64) 作为事务日志文件的寻址空间, 理论上, PG 的事务日志空间最大为 2^64Bytes(即 16EB). 这个大小有多大呢? 假设某个数据库比较繁忙, 每天可以产生 16TB 的日志文件, 那么要达到事务日志文件大小的上限需要的时间是 1024*1024/365 天≈2800 年. 也就是说, 虽然大小有限制, 但从现阶段来看已然足够了.
显然, 对于 16EB 的文件, OS 是无法高效管理的, 为此, PG 把事务日志文件划分为 N 个大小为 16M(默认值)的 WAL segment file, 其总体结构如下图所示:
总体结构
1,WAL segment file
WAL segment file 文件名称为 24 个字符, 由 3 部分组成, 每个部分是 8 个字符, 每个字符是一个 16 进制值(即 0~F). 每一部分的解析如下(在 WAL segment file 文件大小为 16MB 的情况下):
第 1 部分是 TimeLineID, 取值范围是 0x00000000 -> 0xFFFFFFFF
第 2 部分是逻辑文件 ID, 取值范围是 0x00000000 -> 0xFFFFFFFF
第 3 部分是物理文件 ID, 取值范围是 0x00000000 -> 0x000000FF
逻辑文件 ID, 物理文件 ID 和文件大小这三部分的组合, 实现了 64bit 的寻找空间:
逻辑文件 ID 是 32bit 的 uint32(unsigned int 32bit)
物理文件 ID 是 8bit 的 unit8
16M 的文件大小是 24bit 的 unit24
三者共同组成 unit64(32+8+24), 达到最大 64bit 的文件寻址空间.
2, 再谈 LSN
事务日志文件的 LSN 表示 XLOG Record 记录写入到事务日志文件中的位置. LSN 可以理解为 XLOG Record 在事务日志文件中的偏移(Offset).
LSN 由 3 部分组成, 分别是逻辑文件 ID, 物理文件 ID 和文件内偏移. 如 LSN:1/4288E228, 其中 1 为逻辑文件 ID,42 为物理文件 ID,88E228 为 WAL segment file 文件内偏移(注: 3Bytes 的寻找空间为 16MB).
按此规则, 给定一个 LSN, 很容易根据 LSN 号推算得到其对应的日志文件(假定时间线 TimeLineID 为 1).
如: LSN 1/4288E228 对应的 WAL segment file 文件为 00000001 00000001 00000042, 该文件名称的前 8 位为时间线 ID(00000001), 中间 8 位 (00000001) 为逻辑文件 ID, 最后 8 位 (00000042) 为物理文件 ID.
另外, PG 也提供了相应的函数根据 LSN 获取日志文件名:
- testdb=# SELECT pg_walfile_name('1/4288E228');
- pg_walfile_name
- --------------------------
- 000000010000000100000042
- (1 row)
三, WAL segment file 内部结构
WAL segment file 默认大小为 16MB, 其内部结构如下图所示:
WAL segment file 内部结构
1,WAL segment file
WAL segment file 内部划分为 N 个 page(Block), 每个 page 大小为 8192 Bytes 即 8K, 每个 WAL segment file 第 1 个 page 的 header 在 PG 源码中相应的数据结构是 XLogLongPageHeaderData, 后续其他 page 的 header 对应的数据结构是 XLogPageHeaderData. 在一个 page 中, page header 之后是 N 个 XLOG Record.
2,XLOG Record
XLOG Record 由两部分组成, 第一部分是 XLOG Record 的头部信息, 大小固定(24 Bytes), 对应的结构体是 XLogRecord; 第二部分是 XLOG Record data.
XLOG Record 的整体布局如下:
头部数据(固定大小的 XLogRecord 结构体)
XLogRecordBlockHeader 结构体
XLogRecordBlockHeader 结构体
...
XLogRecordDataHeader[Short|Long] 结构体
- block data
- block data
- ...
- main data
XLOG Record 按存储的数据内容来划分, 大体可以分为三类:
Record for backup block: 存储 full-write-page 的 block, 这种类型 Record 是为了解决 page 部分写的问题. 在 checkpoint 完成后第一次修改数据 page, 在记录此变更写入事务日志文件时整页写入(需设置相应的初始化参数, 默认为打开);
Record for tuple data block: 存储 page 中的 tuple 变更, 使用这种类型的 Record 记录;
Record for Checkpoint: 在 checkpoint 发生时, 在事务日志文件中记录 checkpoint 信息(其中包括 Redo point).
其中 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 等日志数据.
以 INSERT 数据为例, 在插入数据时的 XLOG Record data 内部结构如下图所示:
XLOG Record data for DML
3, 数据结构
1) XLogPageHeaderData 结构体定义
- /*
- * Each page of XLOG file has a header like this:
- * 每一个事务日志文件的 page 都有头部信息, 结构如下:
- */
- // 可作为 WAL 版本信息
- #define XLOG_PAGE_MAGIC 0xD098 /* can be used as WAL version indicator */
- typedef struct XLogPageHeaderData
- {
- //WAL 版本信息, PG V11.1 --> 0xD98
- uint16 xlp_magic; /* magic value for correctness checks */
- // 标记位(详见下面说明)
- uint16 xlp_info; /* flag bits, see below */
- //page 中第一个 XLOG Record 的 TimeLineID, 类型为 uint32
- TimeLineID xlp_tli; /* TimeLineID of first record on page */
- //page 的 XLOG 地址(在事务日志中的偏移), 类型为 uint64
- 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.
- * 如果当前页的空间不足以存储整个 XLOG Record, 在下一个页面中存储余下的数据
- * xlp_rem_len 表示上一页 XLOG Record 剩余部分的大小
- *
- * 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.
- * 注意 xl_rem_len 包含 backup-block data(full-page-write);
- * 也就是说在初始的头部信息中跟踪的是 xl_tot_len 而不是 xl_len.
- * 另外要注意的是剩余的数据不需要对齐.
- */
- // 上一页空间不够存储 XLOG Record, 该 Record 在本页继续存储占用的空间大小
- uint32 xlp_rem_len; /* total len of remaining data for record */
- } XLogPageHeaderData;
- #define SizeOfXLogShortPHD MAXALIGN(sizeof(XLogPageHeaderData))
- typedef XLogPageHeaderData *XLogPageHeader;
2) 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.
- * 如设置了 XLP_LONG_HEADER 标记, 在 page header 中存储额外的字段.
- * (通常在每个事务日志文件也就是 segment file 的的第一个 page 中存在).
- * 附加字段用于准确识别文件.
- */
- typedef struct XLogLongPageHeaderData
- {
- // 标准的头部域字段
- XLogPageHeaderData std; /* standard header fields */
- //pg_control 中的系统标识码
- 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;
- /* When record crosses page boundary, set this flag in new page's header */
- // 如果 XLOG Record 跨越 page 边界, 在新 page header 中设置该标志位
- #define XLP_FIRST_IS_CONTRECORD 0x0001
- // 该标志位标明是 "long" 页头
- /* This flag indicates a "long" page header */
- #define XLP_LONG_HEADER 0x0002
- /* This flag indicates backup blocks starting in this page are optional */
- // 该标志位标明从该页起始的 backup blocks 是可选的(不一定存在)
- #define XLP_BKP_REMOVABLE 0x0004
- //xlp_info 中所有定义的标志位(用于 page header 的有效性检查)
- /* All defined flag bits in xlp_info (used for validity checking of header) */
- #define XLP_ALL_FLAGS 0x0007
- #define XLogPageHeaderSize(hdr) \
- (((hdr)->xlp_info & XLP_LONG_HEADER) ? SizeOfXLogLongPHD : SizeOfXLogShortPHD)
3) XLogRecord 结构体定义
- /*
- * The overall layout of an XLOG record is:
- * Fixed-size header (XLogRecord struct)
- * XLogRecordBlockHeader struct
- * XLogRecordBlockHeader struct
- * ...
- * XLogRecordDataHeader[Short|Long] struct
- * block data
- * block data
- * ...
- * main data
- * XLOG record 的整体布局如下:
- * 固定大小的头部(XLogRecord 结构体)
- * XLogRecordBlockHeader 结构体
- * XLogRecordBlockHeader 结构体
- * ...
- * XLogRecordDataHeader[Short|Long] 结构体
- * block data
- * block data
- * ...
- * main data
- *
- * There can be zero or more XLogRecordBlockHeaders, and 0 or more bytes of
- * rmgr-specific data not associated with a block. XLogRecord structs
- * always start on MAXALIGN boundaries in the WAL files, but the REST of
- * the fields are not aligned.
- * 其中, XLogRecordBlockHeaders 可能有 0 或者多个, 与 block 无关的 0 或多个字节的 rmgr-specific 数据
- * XLogRecord 通常在 WAL 文件的 MAXALIGN 边界起写入, 但后续的字段并没有对齐
- *
- * The XLogRecordBlockHeader, XLogRecordDataHeaderShort and
- * XLogRecordDataHeaderLong structs all begin with a single 'id' byte. It's
- * used to distinguish between block references, and the main data structs.
- * XLogRecordBlockHeader/XLogRecordDataHeaderShort/XLogRecordDataHeaderLong 开头是占用 1 个字节的 "id".
- * 用于区分 block 依赖和 main data 结构体.
- */
- typedef struct XLogRecord
- {
- //record 的大小
- uint32 xl_tot_len; /* total len of entire record */
- //xact id
- TransactionId xl_xid; /* xact id */
- // 指向 log 中的前一条记录
- 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 */
- //2 个字节的 crc 校验位, 初始化为 0
- pg_crc32c xl_crc; /* CRC for this record */
- /* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding */
- // 接下来是 XLogRecordBlockHeaders 和 XLogRecordDataHeader
- } XLogRecord;
- // 宏定义: XLogRecord 大小
- #define SizeOfXLogRecord (offsetof(XLogRecord, xl_crc) + sizeof(pg_crc32c))
- /*
- * The high 4 bits in xl_info may be used freely by rmgr. The
- * XLR_SPECIAL_REL_UPDATE and XLR_CHECK_CONSISTENCY bits can be passed by
- * XLogInsert caller. The REST are set internally by XLogInsert.
- * xl_info 的高 4 位由 rmgr 自由使用.
- * XLR_SPECIAL_REL_UPDATE 和 XLR_CHECK_CONSISTENCY 由 XLogInsert 函数的调用者传入.
- * 其余由 XLogInsert 内部使用.
- */
- #define XLR_INFO_MASK 0x0F
- #define XLR_RMGR_INFO_MASK 0xF0
- /*
- * If a WAL record modifies any relation files, in ways not covered by the
- * usual block references, this flag is set. This is not used for anything
- * by PostgreSQL itself, but it allows external tools that read WAL and keep
- * track of modified blocks to recognize such special record types.
- * 如果 WAL 记录使用特殊的方式 (不涉及通常块引用) 更新了关系的存储文件, 设置此标记.
- * PostgreSQL 本身并不使用这种方法, 但它允许外部工具读取 WAL 并跟踪修改后的块,
- * 以识别这种特殊的记录类型.
- */
- #define XLR_SPECIAL_REL_UPDATE 0x01
- /*
- * Enforces consistency checks of replayed WAL at recovery. If enabled,
- * each record will log a full-page write for each block modified by the
- * record and will reuse it afterwards for consistency checks. The caller
- * of XLogInsert can use this value if necessary, but if
- * wal_consistency_checking is enabled for a rmgr this is set unconditionally.
- * 在恢复时强制执行一致性检查.
- * 如启用此功能, 每个记录将为记录修改的每个块记录一个完整的页面写操作, 并在以后重用它进行一致性检查.
- * 在需要时, XLogInsert 的调用者可使用此标记, 但如果 rmgr 启用了 wal_consistency_checking,
- * 则会无条件执行一致性检查.
- */
- #define XLR_CHECK_CONSISTENCY 0x02
4) XLogRecordBlockHeader 结构体定义
- /*
- * Header info for block data appended to an XLOG record.
- * 追加到 XLOG record 中 block data 的头部信息
- *
- * 'data_length' is the length of the rmgr-specific payload data associated
- * with this block. It does not include the possible full page image, nor
- * XLogRecordBlockHeader struct itself.
- * 'data_length'是与此块关联的 rmgr 特定 payload data 的长度.
- * 它不包括可能的 full page image, 也不包括 XLogRecordBlockHeader 结构体本身.
- *
- * Note that we don't attempt to align the XLogRecordBlockHeader struct!
- * So, the struct must be copied to aligned local storage before use.
- * 注意: 我们不打算尝试对齐 XLogRecordBlockHeader 结构体!
- * 因此, 在使用前, XLogRecordBlockHeader 必须拷贝到对齐的本地存储中.
- */
- typedef struct XLogRecordBlockHeader
- {
- // 块引用 ID
- uint8 id; /* block reference ID */
- // 在关系中使用的 fork 和 flags
- uint8 fork_flags; /* fork within the relation, and flags */
- //payload 字节大小
- uint16 data_length; /* number of payload bytes (not including page
- * image) */
- /* If BKPBLOCK_HAS_IMAGE, an XLogRecordBlockImageHeader struct follows */
- /* If BKPBLOCK_SAME_REL is not set, a RelFileNode follows */
- /* BlockNumber follows */
- // 如 BKPBLOCK_HAS_IMAGE, 后续为 XLogRecordBlockImageHeader 结构体
- // 如 BKPBLOCK_SAME_REL 没有设置, 则为 RelFileNode
- // 后续为 BlockNumber
- } XLogRecordBlockHeader;
- #define SizeOfXLogRecordBlockHeader (offsetof(XLogRecordBlockHeader, data_length) + sizeof(uint16))
5) XLogRecordDataHeader[Short|Long]结构体定义
- /*
- * XLogRecordDataHeaderShort/Long are used for the "main data" portion of
- * the record. If the length of the data is Less than 256 bytes, the short
- * form is used, with a single byte to hold the length. Otherwise the long
- * form is used.
- * XLogRecordDataHeaderShort/Long 用于记录的 "main data" 部分.
- * 如果数据的长度小于 256 字节, 则使用短格式, 用一个字节保存长度.
- * 否则使用长形式.
- *
- * (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 */
- // 接下来是无符号 32 位整型的 data_length(未对齐)
- } XLogRecordDataHeaderLong;
- #define SizeOfXLogRecordDataHeaderLong (sizeof(uint8) + sizeof(uint32))
- /*
- * Block IDs used to distinguish different kinds of record fragments. Block
- * references are numbered from 0 to XLR_MAX_BLOCK_ID. A rmgr is free to use
- * any ID number in that range (although you should stick to small numbers,
- * because the WAL machinery is optimized for that case). A couple of ID
- * numbers are reserved to denote the "main" data portion of the record.
- * 块 id 用于区分不同类型的记录片段.
- * 块引用编号从 0 到 XLR_MAX_BLOCK_ID.
- * rmgr 可以自由使用该范围内的任何 ID 号
- * (尽管您应该坚持使用较小的数字, 因为 WAL 机制针对这种情况进行了优化).
- * 保留两个 ID 号来表示记录的 "main" 数据部分.
- *
- * The maximum is currently set at 32, quite arbitrarily. Most records only
- * need a handful of block references, but there are a few exceptions that
- * need more.
- * 目前的最大值是 32, 非常随意.
- * 大多数记录只需要少数块引用, 但也有少数的例外, 需要更多.
- */
- #define XLR_MAX_BLOCK_ID 32
- #define XLR_BLOCK_ID_DATA_SHORT 255
- #define XLR_BLOCK_ID_DATA_LONG 254
- #define XLR_BLOCK_ID_ORIGIN 253
- #endif /* XLOGRECORD_H */
6) xl_heap_header 结构体定义
- /*
- * We don't store the whole fixed part (HeapTupleHeaderData) of an inserted
- * or updated tuple in WAL; we can save a few bytes by reconstructing the
- * fields that are available elsewhere in the WAL record, or perhaps just
- * plain needn't be reconstructed. These are the fields we must store.
- * NOTE: t_hoff could be recomputed, but we may as well store it because
- * it will come for free due to alignment considerations.
- * PG 不会在 WAL 中存储插入 / 更新的元组的全部固定部分(HeapTupleHeaderData);
- * 我们可以通过重新构造在 WAL 记录中可用的一些字段来节省一些空间, 或者直接扁平化处理.
- * 这些都是我们必须存储的字段.
- * 注意: t_hoff 可以重新计算, 但我们也需要存储它, 因为出于对齐的考虑, 会被析构.
- */
- typedef struct xl_heap_header
- {
- uint16 t_infomask2;//t_infomask2 标记
- uint16 t_infomask;//t_infomask 标记
- uint8 t_hoff;//t_hoff
- } xl_heap_header;
- //HeapHeader 的大小
- #define SizeOfHeapHeader (offsetof(xl_heap_header, t_hoff) + sizeof(uint8))
7) xl_heap_insert 结构体定义
- /*
- * xl_heap_insert/xl_heap_multi_insert flag values, 8 bits are available.
- */
- /* PD_ALL_VISIBLE was cleared */
- #define XLH_INSERT_ALL_VISIBLE_CLEARED (1<<0)
- #define XLH_INSERT_LAST_IN_MULTI (1<<1)
- #define XLH_INSERT_IS_SPECULATIVE (1<<2)
- #define XLH_INSERT_CONTAINS_NEW_TUPLE (1<<3)
- /* This is what we need to know about insert */
- // 这是在插入时需要获知的信息
- typedef struct xl_heap_insert
- {
- // 已成功插入的元组的偏移
- OffsetNumber offnum; /* inserted tuple's offset */
- uint8 flags; // 标记
- /* xl_heap_header & TUPLE DATA in backup block 0 */
- //xl_heap_header & TUPLE DATA 在备份块 0 中
- } xl_heap_insert;
- //xl_heap_insert 大小
- #define SizeOfHeapInsert (offsetof(xl_heap_insert, flags) + sizeof(uint8))
四, WAL segment file 内容剖析
下面使用 Linux 下的 hexdump 工具查看 WAL 文件中的内容, 通过查看文件内容可以直观的观察上述的数据结构.
WAL 文件信息
- [xdb@localhost pg_wal]$ ll
- total 32796
- -rw-------. 1 xdb xdb 16777216 Dec 18 10:52 000000010000000100000042
- ...
以下使用 000000010000000100000042 文件为例进行解析.
- 1,XLogPageHeaderData
- uint16 xlp_magic
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 0 -n 2
- 00000000 98 d0 |..|
- 00000002
magic value 为 0xD098.
注意: X86 CPU 使用小端模式(Little-Endian), 高位字节在内存高位地址, 低位字节在内存低位地址.
- uint16 xlp_info
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 2 -n 2
- 00000002 07 00 |..|
- 00000004
xlp_info 标志为 0x0007, 即 XLP_FIRST_IS_CONTRECORD | XLP_LONG_HEADER | XLP_BKP_REMOVABLE
表示:
Ø XLOG Record 跨越 page 边界
Ø 这个 page 的 header 是 XLogLongPageHeaderData
Ø 从该页起始的 backup blocks 是可选的(不一定存在)
- TimeLineID(uint32) xlp_tli
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 4 -n 4
- 00000004 01 00 00 00 |....|
- 00000008
TimeLineID 为 0x00000001, 即十进制数值 1
- XLogRecPtr(uint64) xlp_pageaddr
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 8 -n 8
- 00000008 00 00 00 42 01 00 00 00 |...B....|
- 00000010
XLog Record 在事务日志指针 (偏移) 为 0x00000001 42000000
- uint32 xlp_rem_ln
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 16 -n 4
- 00000010 0f 00 00 00 |....|
- 00000014
上一页空间不足以存储 XLOG Record, 该 Record 在本页继续存储占用的空间大小: 0x0000000F
2,XLogLongPageHeaderData
XLogLongPageHeaderData 的第一个字段是 XLogPageHeaderData, 相关数据参见上述 XLogPageHeaderData 描述.
注意: XLogPageHeaderData 结构体按最大基本类型 (unit64) 对齐, 扩充为 24Bytes(原为 20Bytes, 对齐为 8 Bytes 的倍数), 因此 XLogLongPageHeaderData 的内容从偏移 24 处起算.
- uint64 xlp_sysid
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 24 -n 8
- 00000018 42 72 7f 55 41 76 ee 5b |Br.UAv.[|
- 00000020
系统标识码 0x5BEE7641557F7242
[xdb@localhost ~]((0x5BEE7641557F7242))
6624362124887945794
使用 pg_controldata 查看 Database system identifier-->6624362124887945794
- [xdb@localhost ~]$ pg_controldata
- pg_control version number: 1100
- Catalog version number: 201809051
- Database system identifier: 6624362124887945794
- ...
- uint32 xlp_seg_size
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 32 -n 4
- 00000020 00 00 00 01 |....|
- 00000024
值为 0x01000000, 即 16M
[xdb@localhost ~]((0x01000000))
- 16777216
- uint32 xlp_xlog_blcksz
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 36 -n 4
- 00000024 00 20 00 00 |. ..|
- 00000028
值为 0x00002000, 即 8K
[xdb@localhost ~]((0x00002000))
8192
上一页 XLOG Record 的剩余部分
由于空间不足, 上一 page 的 XLOG Record 在本页继续存储占用的空间(xlp_rem_len=0x0F, 补齐为 16 Bytes)
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 40 -n 16
- 00000028 31 00 00 00 00 00 00 00 00 69 b8 40 25 00 00 00 |1........i.@%...|
- 00000038
- 3,XLogRecord
接下来是 XLOG Record 中的 XLogRecord
- uint32 xl_tot_len
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 56 -n 4
- 00000038 4f 00 00 00 |O...|
- 0000003c
XLOG Record 长度为 0x0000004F
- TransactionId(uint32) xl_xid
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 60 -n 4
- 0000003c 6b 07 00 00 |k...|
- 00000040
事务 ID 为 0x0000076B, 即十进制的 1899
- XLogRecPtr(uint64) xl_pev*
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 64 -n 8
- 00000040 c0 ff ff 41 01 00 00 00 |...A....|
- 00000048
上一个 XLOG Record 的偏移, 即 0x00000001 41FFFFC0
- unit8 xl_info
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 72 -n 1
- 00000048 00 |.|
- 00000049
标志位为 0x00
- unit8 xl_rmid
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 73 -n 1
- 00000049 0a |.|
- 0000004a
该记录的资源管理器, 即 0x0A
- 2 bytes of padding
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 74 -n 2
- 0000004a 00 00 |..|
- 0000004c
- pg_crc32c(uint32) xl_crc
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 76 -n 4
- 0000004c ea 21 d2 50 |.!.P|
- 00000050
CRC 校验位, 即 0x50D221EA
4,XLOG Record data
XLogRecord 之后是 XLOG Record 中的 XLOG Record data
- 1) XLogRecordBlockHeader
- uint8 id
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 80 -n 1
- 00000050 00 |.|
- 00000051
块引用 ID 为 0x00, 即 0 号 Block.
- uint8 fork_flags
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 81 -n 1
- 00000051 20 | |
- 00000052
值为 0x20, 高 4 位用于标记, 即 BKPBLOCK_HAS_DATA
- uint16 data_length
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 82 -n 2
- 00000052 1e 00 |..|
- 00000054
payload bytes = 0x001E, 十进制数值为 30.
RelFileNode
接下来是 RelFileNode, 包括 tablespace/database/relation, 均为 Oid 类型(unsigned int).
Ø tablespace
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 84 -n 4
- 00000054 7f 06 00 00 |....|
- 00000058
值为 0x0000067F, 十进制值为 1663
表空间为 default
- testdb=# select * from pg_tablespace where oid=1663;
- spcname | spcowner | spcacl | spcoptions
- ------------+----------+--------+------------
- pg_default | 10 | |
- (1 row)
Ø database
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 88 -n 4
- 00000058 12 40 00 00 |.@..|
- 0000005c
值为 0x00004012, 十进制值为 16402, 数据库为 testdb
- testdb=# select * from pg_database where oid=16402;
- datname | datdba | encoding | datcollate | datctype | datistemplate | datallowconn | datconnlimit | datlastsysoid | datfroze
- nxid | datminmxid | dattablespace | datacl
- ---------+--------+----------+------------+----------+---------------+--------------+--------------+---------------+---------
- -----+------------+---------------+--------
- testdb | 10 | 6 | C | C | f | t | -1 | 13284 |
- 561 | 1 | 1663 |
- (1 row)
Ø relation
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 92 -n 4
- 0000005c 56 42 00 00 |VB..|
- 00000060
值为 0x00004256, 十进制值为 16982
- testdb=# select oid,relfilenode,relname from pg_class where relfilenode = 16982;
- oid | relfilenode | relname
- -------+-------------+---------
- 16982 | 16982 | t_jfxx
- (1 row)
相应的关系为 t_jfxx
- BlockNumber
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 96 -n 4
- 00000060 85 00 00 00 |....|
- 00000064
值为 0x00000085, 十进制值为 133, 这是对应的数据块号.
2) XLogRecordDataHeaderShort
接下来是 XLogRecordDataHeaderShort/Long, 由于数据小于 256B, 使用 XLogRecordDataHeaderShort 结构
- unit8 id
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 100 -n 1
- 00000064 ff |.|
- 00000065
值为 0xFF --> XLR_BLOCK_ID_DATA_SHORT 255
- uint8 data_length
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 101 -n 1
- 00000065 03 |.|
- 00000066
值为 0x03,3 个字节, 指的是 main data 的大小, 3 个字节是 xl_heap_insert 结构体的大小.
3) block data
XLogRecordDataHeaderShort 之后是 block data, 由两部分组成:
Ø xl_heap_header
Ø Tuple data
xl_heap_header
Ø uint16 t_infomask2
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 102 -n 2
- 00000066 03 00 |..|
- 00000068
t_infomask2 值为 0x03, 二进制值为 00000000 00000011
Ø uint16 t_infomask
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 104 -n 2
- 00000068 02 08 |..|
- 0000006a
t_infomask 值为 0x0802, 二进制值为 00001000 00000010
Ø uint8 t_hoff
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 106 -n 1
- 0000006a 18 |.|
- 0000006b
t_hoff 值 (偏移) 为 0x18, 十进制值为 24
Tuple data
该部分的大小为 25 Bytes:
XLOG Record 的大小是 0x4F 即 79 Bytes, 减去 XLogRecord(24 Bytes) + XLogRecordBlockHeader(20 Bytes) + XLogRecordDataHeaderShort(2 Bytes) + xl_heap_header(5 Bytes) + main data(3 Bytes), 剩余空间大小为 25 Bytes.
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 107 -n 25
- 0000006b 00 0d 32 30 39 31 39 0f 32 30 31 33 30 37 00 00 |..20919.201307..|
- 0000007b 00 00 00 00 00 00 03 b3 40 |........@|
- 00000084
- 4) main data
这部分存储的是 xl_heap_insert 结构体
- uint16 OffsetNumber
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 132 -n 2
- 00000084 26 00 |&.|
- 00000086
插入的 tuple 的偏移为 0x0026, 十进制为 38
- uint8 flags
- [xdb@localhost pg_wal]$ hexdump -C 000000010000000100000042 -s 134 -n 1
- 00000086 00 |.|
- 00000087
标志位为 0x00
五, pg_waldump 工具简介
按照上面几节对事务日志文件结构的介绍, 可以自行写一个解析事务日志的小程序用于查看日志文件中的内容, PG 已提供了 dump 事务日志的工具: pg_waldump(PG 9.x 或以下版本, 使用 pg_xlogdump).
在 Linux 下执行, 使用 --help 查看帮助信息.
- [xdb@localhost pg_wal]$ pg_waldump --help
- pg_waldump decodes and displays PostgreSQL write-ahead logs for debugging.
- Usage:
- pg_waldump [OPTION]... [STARTSEG [ENDSEG]]
- Options:
- -b, --bkp-details output detailed information about backup blocks
- ...
各选项的详细解释可参加 PG Document.
下面以举例的方式简单介绍该工具的使用.
- [xdb@localhost pg_wal]$ ll
- total 98332
- -rw-------. 1 xdb xdb 16777216 Dec 20 12:02 000000010000000100000048
- -rw-------. 1 xdb xdb 16777216 Dec 19 16:47 000000010000000100000049
- -rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004A
- -rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004B
- -rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004C
- -rw-------. 1 xdb xdb 16777216 Dec 19 16:47 00000001000000010000004D
- drwx------. 2 xdb xdb 6 Nov 16 15:48 archive_status
以上为数据库服务器上的事务日志文件.
例一: 查看 000000010000000100000048 最早的 4 个 XLOG Record
命令: pg_waldump -p ./ -s 1/48000000 -n 4
- [xdb@localhost pg_wal]$ pg_waldump -p ./ -s 1/48000000 -n 4
- rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/48000070, prev 1/47FFFFF8, desc: INSERT off 117, blkref #0: rel 1663/16402/17028 blk 1110
- rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/480000C0, prev 1/48000070, desc: INSERT off 7, blkref #0: rel 1663/16402/17031 blk 1111
- rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/48000110, prev 1/480000C0, desc: INSERT off 8, blkref #0: rel 1663/16402/17031 blk 1111
- rmgr: Heap len (rec/tot): 77/ 77, tx: 1964, lsn: 1/48000160, prev 1/48000110, desc: INSERT off 9, blkref #0: rel 1663/16402/17031 blk 1111
注意第一条记录, 上一个 LSN 为 1/47FFFFF8(prev 1/47FFFFF8), 表示上一 page 最后一个 XLOG Record 存储在本页的 XLogLongPageHeaderData 中, 存储的空间大小可以从该 XLOG Record 的 LSN(1/48000070)和 XLogLongPageHeaderData 的大小 (40 Bytes) 推算而得.
例二: 查看 Redo point 后的 XLOG Record
首先使用 pg_controldata 命令查看 Redo point --> 1/484336A0
- [xdb@localhost pg_wal]$ pg_controldata
- pg_control version number: 1100
- Catalog version number: 201809051
- Database system identifier: 6624362124887945794
- Database cluster state: in production
- pg_control last modified: Thu 20 Dec 2018 12:17:39 PM CST
- Latest checkpoint location: 1/484336D8
- Latest checkpoint's REDO location: 1/484336A0
- Latest checkpoint's REDO WAL file: 000000010000000100000048
- Latest checkpoint's TimeLineID: 1
- ...
然后使用 pg_waldump 查看
命令: pg_waldump -p ./ -s 1/484336A0
- [xdb@localhost pg_wal]$ pg_waldump -p ./ -s 1/484336A0
- rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 1/484336A0, prev 1/48433668, desc: RUNNING_XACTS nextXid 1971 latestCompletedXid 1970 oldestRunningXid 1971
- rmgr: XLOG len (rec/tot): 106/ 106, tx: 0, lsn: 1/484336D8, prev 1/484336A0, desc: CHECKPOINT_ONLINE redo 1/484336A0; tli 1; prev tli 1; fpw true; xid 0:1971; oid 17046; multi 1; offset 0; oldest xid 561 in DB 16402; oldest multi 1 in DB 16402; oldest/newest commit timestamp xid: 0/0; oldest running xid 1971; online
- rmgr: Standby len (rec/tot): 50/ 50, tx: 0, lsn: 1/48433748, prev 1/484336D8, desc: RUNNING_XACTS nextXid 1971 latestCompletedXid 1970 oldestRunningXid 1971
- pg_waldump: FATAL: error in WAL record at 1/48433748: invalid record length at 1/48433780: wanted 24, got 0
- [xdb@localhost pg_wal]$
六, 参考资料
- ,Write Ahead Logging - WAL http://www.interdb.jp/pg/pgsql09.html
- ,PG Source Code https://doxygen.postgresql.org/
- ,WAL Internals Of PostgreSQL
4, 关于结构体占用空间大小总结
5,PG 11 Document https://www.postgresql.org/docs/11/pgwaldump.html
来源: http://www.jianshu.com/p/b9087d9f20e2