searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

解析XLog:使用python通过外部解析XLog

2023-08-09 01:33:03
202
0

0.思考

初衷就是想深入xlog内部,想具体看一看wal内部到底存储着什么内容,为什么可以通过wal日志恢复所有的数据信息,包括为什么他可以实现主从复制等一系列操作

最终目的:拆解wal日志

1.wal在源码中的组装

1.1 查看xlog

我们可以使用pg_waldump查看wal信息

/usr/local/postgresql/bin/pg_waldump -p /usr/local/postgresql/data/pg_wal/ 000000010000000000000077 -n 10
[root@ecs-51225917 pg_wal]# /usr/local/postgresql/bin/pg_waldump -p /usr/local/postgresql/data/pg_wal/ 000000010000000000000001 -n 10
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 0/01000028, prev 0/00000000, desc: CHECKPOINT_SHUTDOWN redo 0/1000028; tli 1; prev tli 1; fpw true; xid 0:3; oid 12000; multi 1; offset 0; oldest xid 3 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
rmgr: XLOG        len (rec/tot):     30/    30, tx:          1, lsn: 0/010000A0, prev 0/01000028, desc: NEXTOID 20192
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010000C0, prev 0/010000A0, desc: FPI , blkref #0: rel 1663/1/6117 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000148, prev 0/010000C0, desc: FPI , blkref #0: rel 1664/0/6115 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010001D0, prev 0/01000148, desc: FPI , blkref #0: rel 1664/0/6114 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000258, prev 0/010001D0, desc: FPI , blkref #0: rel 1663/1/6113 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010002E0, prev 0/01000258, desc: FPI , blkref #0: rel 1663/1/6112 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000368, prev 0/010002E0, desc: FPI , blkref #0: rel 1663/1/6111 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010003F0, prev 0/01000368, desc: FPI , blkref #0: rel 1663/1/6110 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000478, prev 0/010003F0, desc: FPI , blkref #0: rel 1663/1/3351 blk 0 FPW
rmgr 资源名称
lsn 日志编号
prev 前一段编号
desc 对日志的详细描述
xid 事务id

1.2 wal初始化

初始化XLOG的函数入口为BootStrapXLOG

source:src/backend/access/transam/xlog.c
BootStrapXLOG(void)
 
/*
 * This func must be called ONCE on system install.  It creates pg_control
 * and the initial XLOG segment.
 */
void
BootStrapXLOG(void)
{
	CheckPoint	checkPoint;
	char	   *buffer;
	XLogPageHeader page;
	XLogLongPageHeader longpage;
	XLogRecord *record;
	char	   *recptr;
	bool		use_existent;
	uint64		sysidentifier;
	char		mock_auth_nonce[MOCK_AUTH_NONCE_LEN];
	struct timeval tv;
	pg_crc32c	crc;

这个函数只会在初始化的时候调用一次,用来创建控制文件和初始化XLOG segment。

我们先看看第一个XLOG文件名称生成:

#define XLogFilePath(path, tli, logSegNo, wal_segsz_bytes)	\
	snprintf(path, MAXPGPATH, XLOGDIR "/%08X%08X%08X", tli,	\
			 (uint32) ((logSegNo) / XLogSegmentsPerXLogId(wal_segsz_bytes)), \
			 (uint32) ((logSegNo) % XLogSegmentsPerXLogId(wal_segsz_bytes)))

XLogSegmentsPerXLogId:

#define XLogSegmentsPerXLogId(wal_segsz_bytes)	\
	(UINT64CONST(0x100000000) / (wal_segsz_bytes))
XLogSegmentsPerXLogId = (0x100000000UL)/(1024*1024*16) = 256`
 

所以最大的XLOG文件名称为:00000001FFFFFFFF000000FF,而不是理论上的00000001FFFFFFFFFFFFFFFF,因为uint64 % 256 最大是FF。

初始化第一个timelineID = 1

/* First timeline ID is always 1 */
	ThisTimeLineID = 1;
 

还是在xlog.c这个文件中说明创建这个文件之间会先创建一个临时文件,

/*
	 * Initialize an empty (all zeroes) segment.  NOTE: it is possible that
	 * another process is doing the same thing.  If so, we will end up
	 * pre-creating an extra log segment.  That seems OK, and better than
	 * holding the lock throughout this lengthy process.
	 */
	elog(DEBUG2, "creating and filling new WAL file");

	snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());

pg_wal/xlogtemp.xxxxxxx("xlogtemp."加个当前进程PID),避免其他process来搞事情,然后循环写入每次写入XLOG_BLOCKSZ个字节

if (wal_init_zero)
	{
		/*
		 * Zero-fill the file.  With this setting, we do this the hard way to
		 * ensure that all the file space has really been allocated.  On
		 * platforms that allow "holes" in files, just seeking to the end
		 * doesn't allocate intermediate space.  This way, we know that we
		 * have all the space and (after the fsync below) that all the
		 * indirect blocks are down on disk.  Therefore, fdatasync(2) or
		 * O_DSYNC will be sufficient to sync future writes to the log file.
		 */
		for (nbytes = 0; nbytes < wal_segment_size; nbytes += XLOG_BLCKSZ)
		{
			errno = 0;
			if (write(fd, zbuffer.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
			{
				/* if write didn't set errno, assume no disk space */
				save_errno = errno ? errno : ENOSPC;
				break;
			}
		}
	}

写完后文件大小为16M

刚才创建的文件其实是一个空文件,随后通过durable_link_or_rename进行rename。

/*
 * durable_link_or_rename -- rename a file in a durable manner.
 *
 * Similar to durable_rename(), except that this routine tries (but does not
 * guarantee) not to overwrite the target file.
 *
 * Note that a crash in an unfortunate moment can leave you with two links to
 * the target file.
 *
 * Log errors with the caller specified severity.
 *
 * Returns 0 if the operation succeeded, -1 otherwise. Note that errno is not
 * valid upon return.
 */
int
durable_link_or_rename(const char *oldfile, const char *newfile, int elevel)
{

到这里,XLOG就创建完成了,不过这个文件还是空空如也

1.3 wal写入

1.3.1 xlog-header

首先写入的是XLOG的头(XLogPageHeader)。

/*
 * Each page of XLOG file has a header like this:
 */
#define XLOG_PAGE_MAGIC 0xD101	/* 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;

这个page_magic也可以用来验证是否是 一个page的开头

每个XLogSegment的第一个page头会写入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;

xlp_seg_size是当前xlog_segment大小=1024*1024*16;

xlp_xlog_blcksz是当前xlog的page大小=8192。结果如下:

这个page的前40个字节就是XLogLongPageHeaderData。写入完之后整个的XLOG_SEGMENT的结构如下图所示:

但是 有一个问题 就是为什么是40B?这里先按下不表,后面解析的时候会遇到问题。

1.3.2 record

日志之间有链接关系,xl_prev指向上一条日志的起始位置,下一条日志的位置用xl_tot_len可以找到,日志之间形成“双向链表”。

 

typedef struct XLogRecord
{
【日记长度】
	uint32		xl_tot_len;		/* total len of entire record */
【事务ID】
	TransactionId xl_xid;		/* xact id */
【上一条日志的LSN】
	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_info低4位保存flag信息,高4位保存日志动作信息。

XLR_INFO_MASKXLR_RMGR_INFO_MASK

    • XLR_INFO_MASK用于提取xl_info字段的低4位,该字段包含一些控制信息。
    • XLR_RMGR_INFO_MASK用于提取xl_info字段的高4位,这些位由rmgr(资源管理器)自由使用
/*
* 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.
*/
#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.
 */
#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.
 */
#define XLR_CHECK_CONSISTENCY	0x02

 

高四位:比如HEAP操作,对应8种动作信息

#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

后续解释,他会用一个很巧妙的形式找到当前的操作

数据主要分两部分:

  • 页面信息:没有实际数据,只有页面相关的信息
  • 数据信息:紧跟着XlogRecordDataHeader后面存储,
      • 实际数据<255字节,使用XLogRecordDataHeaderShort,用1字节保存数据长度
      • 否则使用XLogRecordDataHeaderLong,用4字节保存数据长度
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))

1.3.3 写入断点调试

testdb=# INSERT INTO sytest VALUES ('A');

通过发出上述语句,调用内部函数exec_simple_query()。
exec_simple_query()的伪代码如下所示:

exec_simple_query() @postgres.c

(1) ExtendCLOG() @clog.c                  写入当前事务状态"IN_PROGRESS"
(2) heap_insert()@heapam.c                当前元祖插入共享缓冲池的page
(3)   XLogInsert() @xlog.c (9.5 or later, xloginsert.c)
                                          写入LSN_1的wal缓冲区,
											将修改后的页面的pd_lsn从LSN_0更新为LSN_1。
(4) finish_xact_command() @postgres.c       
      XLogInsert() @xloginsert.c  
                                          将此记录写入LSN_2的WAL缓冲区。
(5)   XLogWrite() @xlog.c                 将WAL缓冲区上的所有XLOG记录刷新到WAL段文件。
(6) TransactionIdCommitTree() @transam.c  将此事务的状态从“IN_PROGRESS”更改为“COMMITTED”。

1.4 rmgr资源管理器

XLog日志被划分为多个类型的资源管理器,每个资源管理器只需要负责与自己相关的日志处理(抽象出操作函数,不同的日志实现不同的操作函数)。资源管理器的定义如下所示:

typedef struct RmgrData {
	const char *rm_name;     // 资源名称
	void		(*rm_redo) (XLogReaderState *record);  // 重做函数
	void		(*rm_desc) (StringInfo buf, XLogReaderState *record);  // 负责解析对应资源事务日志
	const char *(*rm_identify) (uint8 info);                           // 解析xlog记录中的Info字段
	void		(*rm_startup) (void);                                  // 启动时的初始化工作
	void		(*rm_cleanup) (void);                                  // 结束时的清理工作
	void		(*rm_mask) (char *pagedata, BlockNumber blkno);        // 在对页面做一致性检查前,是否需要对页面的某些部分做掩码
} RmgrData;
extern const RmgrData RmgrTable[];

据xlog中的xl_rmid调用资源管理器中不同资源的rm_redo回放函数进行回放。

/* symbol name, textual name, redo, desc, identify, startup, cleanup */
PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, xlog_identify, NULL, NULL, NULL)
PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, xact_identify, NULL, NULL, NULL)
PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, smgr_identify, NULL, NULL, NULL)
PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, clog_identify, NULL, NULL, NULL)
PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, dbase_identify, NULL, NULL, NULL)
PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, tblspc_identify, NULL, NULL, NULL)
PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, multixact_identify, NULL, NULL, NULL)
PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, relmap_identify, NULL, NULL, NULL)
PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, standby_identify, NULL, NULL, NULL)
PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, heap2_identify, NULL, NULL, heap_mask)
PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, heap_identify, NULL, NULL, heap_mask)
PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, NULL, NULL, btree_mask)
PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask)
PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup, gin_mask)
PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup, gist_mask)
PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL, seq_mask)
PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup, spg_mask)
PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL, brin_mask)
PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL)
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL)

在 PostgreSQL 内核中,rmgrlist.h 文件定义了各种类型的 rmgr(record manager),每个 rmgr 对应一个不同的数据库操作。这些 rmgr 在事务日志(XLog)中起着重要作用,用于记录不同类型的数据库操作,以便在之后的恢复过程中重做(redo)或撤销(undo)这些操作。

  1. RM_XLOG_ID: 这是 XLog 自身的 rmgr,负责记录 XLog 中的操作,用于数据库的恢复和故障转移。
  2. RM_XACT_ID: 这个 rmgr 负责记录事务提交和回滚的操作,用于数据库的一致性和事务管理。
  3. RM_SMGR_ID: 这个 rmgr 负责记录存储管理器(SMGR)相关的操作,用于数据库的物理存储管理。
  4. RM_CLOG_ID: 这个 rmgr 负责记录提交日志(CLOG)相关的操作,用于事务的可见性和并发控制。
  5. RM_DBASE_ID: 这个 rmgr 负责记录数据库的创建和删除操作。
  6. RM_TBLSPC_ID: 这个 rmgr 负责记录表空间的创建和删除操作。
  7. RM_MULTIXACT_ID: 这个 rmgr 负责记录多版本并发控制(MVCC)相关的操作。
  8. RM_RELMAP_ID: 这个 rmgr 负责记录关系映射(RelMap)相关的操作。
  9. RM_STANDBY_ID: 这个 rmgr 负责记录热备相关的操作。
  10. RM_HEAP2_ID: 这个 rmgr 也负责记录堆表(Heap)的修改操作。
  11. RM_HEAP_ID: 这个 rmgr 负责记录堆表(Heap)的修改操作,包括插入、更新和删除等。
  12. RM_BTREE_ID: 这个 rmgr 负责记录 B 树索引的修改操作。
  13. RM_HASH_ID: 这个 rmgr 负责记录哈希索引的修改操作。
  14. RM_GIN_ID: 这个 rmgr 负责记录 GIN 索引的修改操作。
  15. RM_GIST_ID: 这个 rmgr 负责记录 GIST 索引的修改操作。
  16. RM_SEQ_ID: 这个 rmgr 负责记录序列(Sequence)的修改操作。
  17. RM_SPGIST_ID: 这个 rmgr 负责记录 SP-GiST 索引的修改操作。
  18. RM_BRIN_ID: 这个 rmgr 负责记录 BRIN 索引的修改操作。
  19. RM_COMMIT_TS_ID: 这个 rmgr 负责记录提交时间戳的修改操作。
  20. RM_REPLORIGIN_ID: 这个 rmgr 负责记录复制源的修改操作。
  21. RM_GENERIC_ID: 这个 rmgr 用于实验性目的,可用于自定义类型的操作。
  22. RM_LOGICALMSG_ID: 这个 rmgr 用于逻辑复制相关的操作。
  • symname: 资源管理器的枚举类型名称。
  • name: 资源管理器的名称,通常与rmgr的功能相关。
  • redo: 资源管理器的重做函数,用于在数据库恢复时重做操作。
  • desc: 资源管理器的描述,对资源管理器进行简要说明。
  • identify: 资源管理器的识别函数,用于识别给定XLog记录类型所属的资源管理器。
  • startup: 资源管理器的启动函数,在数据库启动时执行。
  • cleanup: 资源管理器的清理函数,在数据库关闭时执行。
  • mask: 资源管理器的掩码,用于标识资源管理器是否支持某些特定功能。

heap_mask 函数是 PostgreSQL 内核中用于在执行一致性检查之前对堆页(heap page)进行掩码处理的函数。在执行恢复操作时,数据库会读取 XLog 记录中的内容,并应用到对应的数据页上。为了保证恢复的正确性,必须对数据页进行一致性检查,而 heap_mask 就是用于在执行这些检查之前,对数据页进行掩码处理,从而屏蔽掉不应该参与一致性检查的部分。

以下是 heap_mask 函数的主要功能:

  1. mask_page_lsn_and_checksum:掩码处理数据页的 LSN(Log Sequence Number)和校验和字段,这些字段在数据页恢复时不应该被检查。
  2. mask_page_hint_bits:掩码处理数据页的提示位(hint bits),这些位可能在恢复过程中有变化,但不影响数据页的一致性。
  3. mask_unused_space:掩码处理数据页的未使用空间,这些空间在一致性检查中不应该被考虑。
  4. 对数据页中的每个条目进行处理:
    • 如果是普通的堆元组(HeapTuple),则对其中的某些字段进行掩码,例如事务标识(xmin、xmax)和命令标识(cid)等。在恢复过程中,这些字段可能会有变化,但不影响数据页的一致性。
    • 忽略特定情况下的堆元组的“ctid”字段,用于支持 speculative insertion,这是一种特殊的插入方式。
    • 忽略堆元组中不满足 MAXALIGN 对齐要求的填充字节。

总的来说,heap_mask 函数的目的是为了屏蔽掉在恢复过程中可能出现的无关变化,保证数据页在进行一致性检查时具有稳定的状态。这样可以确保数据恢复的正确性,并且避免因为恢复过程中的变化导致一致性检查失败。

那为什么 有的有mask 有的没有mask?

RMGR可以分为两类:

  1. 有Mask Function的RMGR:
    • 这些RMGR处理的是数据表(如堆表、B树索引、哈希索引、GIN索引等)的重做操作。
    • 在进行WAL记录的重做时,可能会存在一些可允许的差异,例如提示信息、标记位等,这些差异不会影响数据的一致性,因此需要使用Mask Function将这些差异屏蔽掉,只关注可能导致数据不一致的重要内容。
    • 使用Mask Function可以提高一致性检查的灵活性,减少误报,同时确保数据的准确性。
  1. 没有Mask Function的RMGR:
    • 这些RMGR处理的是一些不需要考虑差异的重做操作,或者重做操作本身就足够保证数据的一致性。
    • 比如XLOG记录的重做、事务的重做、存储管理器的重做等,这些操作在重做时不需要考虑额外的Mask操作,因为它们本身已经具有足够的机制来保证数据的正确性。

我自己的理解:

在实现WAL一致性检查功能时,只有涉及数据表的RMGR才需要使用Mask Function,而对于其他类型的RMGR,由于其重做操作本身已经保证了数据的正确性,因此不需要添加Mask Function。

通过区分需要Mask的RMGR和不需要Mask的RMGR,可以在WAL一致性检查过程中准确地找出可能导致数据不一致的地方,并对数据库的一致性进行严格的验证。这样,复制过程中出现的潜在问题可以及早被检测出来,并及时处理,保障数据库系统的可靠性和稳定性。

2.解析wal日志

结果展示

-----------------LONGHEADER------------------------
xlp_magic: D101
xlp_info: 7
xlp_tli: 1
xlp_pageaddr: 77000000
xlp_rem_len: 1257
xlp_sysid: 64B6A0F56CFBE0AA
xlp_seg_size: 1000000
xlp_seg_size in MB: 16.0
xlp_xlog_blcksz: 2000
xlp_xlog_blcksz in KB: 8.0
---------------RECORD-----------------------
xl_tot_len: F5
xl_xid: A048
xl_prev: 76FFF238
xl_info: 0
xl_rmid: A
xl_crc: AA2568A1
---------------INFO-----------------------
当前info:  0
current opera --> insert
---------------XLogRecordBlockHeader-----------------------
id: 00
fork_flags: 20
data_length: 00C4
ref.spcNode: 0000067F
ref.dbNode: 0000401C
ref.relNode: 000040FE
blockNumber: 0000151D
---------------XLogRecordDataHeaderShort-----------------------
id: 255
data_length: 3
---------------xl_heap_header-----------------------
t_infomask2: 4
t_infomask: 2050
t_hoff: 24

 

2.1 结构定义

import ctypes
import sys
class XlogPageHeaderDate(ctypes.Structure):
    _fields_ = [
        ('xlp_magic',ctypes.c_uint16),
        ('xlp_info',ctypes.c_uint16),
        ('xlp_tli',ctypes.c_uint32),
        ('xlp_pageaddr',ctypes.c_uint64),
        ('xlp_rem_len',ctypes.c_uint32),
        ('xlp_al',ctypes.c_uint32) #用于对齐的4个byte
    ]
class XLogLongPageHeaderData(ctypes.Structure):
    _fields_ = [
        ('std', XlogPageHeaderDate),
        ('xlp_sysid', ctypes.c_uint64),
        ('xlp_seg_size', ctypes.c_uint32),
        ('xlp_xlog_blcksz', ctypes.c_uint32)
    ]

class XLogRecord(ctypes.Structure):
    _fields_ = [
        ('xl_tot_len', ctypes.c_uint32),
        ('xl_xid', ctypes.c_uint32),
        ('xl_prev', ctypes.c_uint64),
        ('xl_info', ctypes.c_uint8),
        ('xl_rmid', ctypes.c_uint8),
        ('_padding', ctypes.c_uint16),  # 2 bytes of padding, initialize to zero
        ('xl_crc', ctypes.c_uint32),
    ]

class RelFileNode(ctypes.Structure):  
    _fields_ = [  
        ('spcNode', ctypes.c_uint32),  
        ('dbNode', ctypes.c_uint32),  
        ('relNode', ctypes.c_uint32),  
    ]

class XLogRecordBlockHeader(ctypes.Structure):  
    _fields_ = [  
        ('id', ctypes.c_uint8),  
        ('fork_flags', ctypes.c_uint8),  
        ('data_length', ctypes.c_uint16), 
        ('ref',  RelFileNode),
        ('block_num', ctypes.c_int32),
    ]

class XLogRecordDataHeaderShort(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_uint8),
        ('data_length', ctypes.c_uint8),
    ]

class XLHeapHeader(ctypes.Structure):
    _fields_ = [
        ('t_infomask2', ctypes.c_uint16),
        ('t_infomask', ctypes.c_uint16),
        ('t_hoff',ctypes.c_ubyte)
        # ('t_hoff', ctypes.c_uint8),
    ]

问题一 为什么XLogLongPageHeaderData 一搜都是40B ?

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 */--32
	XLogRecPtr	xlp_pageaddr;	/* XLOG address of this page */--64
	uint32		xlp_rem_len;	/* total len of remaining data for record */
} XLogPageHeaderData;

那这一段加起来应该是 2+2+4+8+4 = 20B

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;

这一段应该是 20+8+4+4 = 36 也不应该是40啊?

仔细阅读源码,发现问题

在XLogPageHeaderData底下出现了这个定义

#define SizeOfXLogShortPHD	MAXALIGN(sizeof(XLogPageHeaderData))

这一块定义了XLogPageHeaderData的长度

分析一下 MAXALIGN---其实读英文名就可以知道 他的意思是对齐 最大

#define MAXALIGN(LEN)			TYPEALIGN(MAXIMUM_ALIGNOF, (LEN))

简单看来 是一个求取长度的内容

/* ----------------
 * Alignment macros: align a length or address appropriately for a given type.
 * The fooALIGN() macros round up to a multiple of the required alignment,
 * while the fooALIGN_DOWN() macros round down.  The latter are more useful
 * for problems like "how many X-sized structures will fit in a page?".
 *
 * NOTE: TYPEALIGN[_DOWN] will not work if ALIGNVAL is not a power of 2.
 * That case seems extremely unlikely to be needed in practice, however.
 *
 * NOTE: MAXIMUM_ALIGNOF, and hence MAXALIGN(), intentionally exclude any
 * larger-than-8-byte types the compiler might have.
 * ----------------
 */

这个上面的内容就是 PG内部做了一个对齐宏,类似于 只要是PG内部的东西 基本上就按照这个对齐宏走

其中 MAXIMUM_ALIGNOF 这个是获得当前编译器中占最大位的基本类型,比如long long类型

取得的long long类型的大小为8BYTE,所以在这个宏定义当中,MAXIMUM_ALIGNOF=8

#define TYPEALIGN(ALIGNVAL,LEN)  \
	(((uintptr_t) (LEN) + ((ALIGNVAL) - 1)) & ~((uintptr_t) ((ALIGNVAL) - 1)))
  1. ALIGNVAL = 8 = 1000
  2. ~((uintptr_t) ((ALIGNVAL) - 1)) = 1000
  3. 前面的部分(((uintptr_t) (LEN) + ((ALIGNVAL) - 1))就是20+7 =27 ,转化为二进制为 0001 1011。
  4. 与第(3)步的计算值进行&运算,得到0001 1000,转换为十进制为24。所以最初的宏SizeOfXLogLongPHD的大小为24

MAXALIGN 的作用其实就是为了使sizeof( struct )向上对齐,成为8的倍数的大小

2.2 逻辑写入位置与物理写入位置

思考为什么会有rem等问题,如果这个操作的tuple内容很多怎么办,或者当前record没有填满怎么办

按道理来说,他的存储应该排列整齐的样子

static void
ReserveXLogInsertLocation(int size, XLogRecPtr *StartPos, XLogRecPtr *EndPos,
						  XLogRecPtr *PrevPtr)
{
	XLogCtlInsert *Insert = &XLogCtl->Insert;
	uint64		startbytepos;
	uint64		endbytepos;
	uint64		prevbytepos;

	size = MAXALIGN(size);
  
  ...
    
	SpinLockAcquire(&Insert->insertpos_lck);

	startbytepos = Insert->CurrBytePos;
	endbytepos = startbytepos + size;
	prevbytepos = Insert->PrevBytePos;
	Insert->CurrBytePos = endbytepos;
	Insert->PrevBytePos = startbytepos;

	SpinLockRelease(&Insert->insertpos_lck);

	*StartPos = XLogBytePosToRecPtr(startbytepos);
	*EndPos = XLogBytePosToEndRecPtr(endbytepos);
	*PrevPtr = XLogBytePosToRecPtr(prevbytepos);

  ...
}

 

#define UsableBytesInPage (XLOG_BLCKSZ - SizeOfXLogShortPHD)
#define UsableBytesInSegment ((XLOG_SEG_SIZE / XLOG_BLCKSZ) * UsableBytesInPage - (SizeOfXLogLongPHD - SizeOfXLogShortPHD))

// UsableBytesInSegment = (XLOG_SEG_SIZE / XLOG_BLCKSZ) * UsableBytesInPage - (SizeOfXLogLongPHD - SizeOfXLogShortPHD)
// UsableBytesInSegment = (16 * 1024 * 1024 / 8192) * (8192 - SizeOfXLogShortPHD) - (XlogHeaderDiff)
// UsableBytesInSegment = 页面数量 * 每个页面除了HEADER之外的空间 - 第一个页面的HEADER多出来的一部分
// UsableBytesInSegment = 一个SEG内能用来保存XLOG的空间总大小
// UsableBytesInSegment = 16728048
// 如果没有HEADER,UsableBytesInSegment = 16 * 1024 * 1024 = 16777216

 他会根据逻辑地址算出物理插入地址,也就是说,物理地址并不会像逻辑地址那样整齐排列。

2.3判断rmgr操作类型

拿到对应的info后,应该怎么判断它属于什么操作,增删改查?

uint8				info;
	
	info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
	info &= XLOG_HEAP_OPMASK;
	
	if (XLOG_HEAP_INSERT == info)
	{
		插入
	}
	else if(XLOG_HEAP_DELETE == info)
	{
		删除
	}
	else if (XLOG_HEAP_UPDATE == info)
	{
		更新
	}
	else if (XLOG_HEAP_HOT_UPDATE == info)
	{
		更新
	}
def judge_info(info_num):
    info = info_num & ~XLR_INFO_MASK
    info &= XLOG_HEAP_OPMASK

    if XLOG_HEAP_INSERT == info:
        print('current opera --> insert')
    elif XLOG_HEAP_DELETE == info:
        print('current opera --> delete')
    elif XLOG_HEAP_UPDATE == info or XLOG_HEAP_HOT_UPDATE == info:
        print('current opera --> update')
# rmid定义判断
RM_XLOG_ID = 0x00
RM_XACT_ID = 0x01
RM_SMGR_ID = 0x02
RM_CLOG_ID = 0x03
RM_DBASE_ID = 0x04
RM_TBLSPC_ID = 0x05
RM_MULTIXACT_ID = 0x06
RM_RELMAP_ID = 0x07
RM_STANDBY_ID = 0x08
RM_HEAP2_ID = 0x09
RM_HEAP_ID = 0x0A
RM_BTREE_ID = 0x0B
RM_HASH_ID = 0x0C
RM_GIN_ID = 0x0D
RM_GIST_ID = 0x0E
RM_SEQ_ID = 0x0F
RM_SPGIST_ID = 0x10
RM_BRIN_ID = 0x11
RM_COMMIT_TS_ID = 0x12
RM_REPLORIGIN_ID = 0x13
RM_GENERIC_ID = 0x14
RM_LOGICALMSG_ID = 0x15

2.4 读取wal

def read_wal(path):
    with open(path,mode='rb') as f:
        buf = f.read(XLOG_BLCKSZ)
        # print(buf)
        hdr = buf[:XLOG_HDRLEN]
        # hdr = buf
        lhdr = buf[:XLOG_LONGHDRLEN]
        rem = buf[XLOG_LONGHDRLEN:XLOG_REM+XLOG_LONGHDRLEN] # 看看是不是page的第一个!!!
        record = buf[XLOG_REM+XLOG_LONGHDRLEN : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD]
        rbh = buf[XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER]
        rdhs = buf[XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER 
                  : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER + XLOGRECORDDATAHEADERSHORT]
        xhh = buf[XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER + XLOGRECORDDATAHEADERSHORT 
                  : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER + XLOGRECORDDATAHEADERSHORT + XLOGHEAPHEADER +1]

 

0条评论
0 / 1000
f****n
3文章数
0粉丝数
f****n
3 文章 | 0 粉丝
f****n
3文章数
0粉丝数
f****n
3 文章 | 0 粉丝
原创

解析XLog:使用python通过外部解析XLog

2023-08-09 01:33:03
202
0

0.思考

初衷就是想深入xlog内部,想具体看一看wal内部到底存储着什么内容,为什么可以通过wal日志恢复所有的数据信息,包括为什么他可以实现主从复制等一系列操作

最终目的:拆解wal日志

1.wal在源码中的组装

1.1 查看xlog

我们可以使用pg_waldump查看wal信息

/usr/local/postgresql/bin/pg_waldump -p /usr/local/postgresql/data/pg_wal/ 000000010000000000000077 -n 10
[root@ecs-51225917 pg_wal]# /usr/local/postgresql/bin/pg_waldump -p /usr/local/postgresql/data/pg_wal/ 000000010000000000000001 -n 10
rmgr: XLOG        len (rec/tot):    114/   114, tx:          0, lsn: 0/01000028, prev 0/00000000, desc: CHECKPOINT_SHUTDOWN redo 0/1000028; tli 1; prev tli 1; fpw true; xid 0:3; oid 12000; multi 1; offset 0; oldest xid 3 in DB 1; oldest multi 1 in DB 1; oldest/newest commit timestamp xid: 0/0; oldest running xid 0; shutdown
rmgr: XLOG        len (rec/tot):     30/    30, tx:          1, lsn: 0/010000A0, prev 0/01000028, desc: NEXTOID 20192
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010000C0, prev 0/010000A0, desc: FPI , blkref #0: rel 1663/1/6117 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000148, prev 0/010000C0, desc: FPI , blkref #0: rel 1664/0/6115 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010001D0, prev 0/01000148, desc: FPI , blkref #0: rel 1664/0/6114 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000258, prev 0/010001D0, desc: FPI , blkref #0: rel 1663/1/6113 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010002E0, prev 0/01000258, desc: FPI , blkref #0: rel 1663/1/6112 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000368, prev 0/010002E0, desc: FPI , blkref #0: rel 1663/1/6111 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/010003F0, prev 0/01000368, desc: FPI , blkref #0: rel 1663/1/6110 blk 0 FPW
rmgr: XLOG        len (rec/tot):     49/   129, tx:          1, lsn: 0/01000478, prev 0/010003F0, desc: FPI , blkref #0: rel 1663/1/3351 blk 0 FPW
rmgr 资源名称
lsn 日志编号
prev 前一段编号
desc 对日志的详细描述
xid 事务id

1.2 wal初始化

初始化XLOG的函数入口为BootStrapXLOG

source:src/backend/access/transam/xlog.c
BootStrapXLOG(void)
 
/*
 * This func must be called ONCE on system install.  It creates pg_control
 * and the initial XLOG segment.
 */
void
BootStrapXLOG(void)
{
	CheckPoint	checkPoint;
	char	   *buffer;
	XLogPageHeader page;
	XLogLongPageHeader longpage;
	XLogRecord *record;
	char	   *recptr;
	bool		use_existent;
	uint64		sysidentifier;
	char		mock_auth_nonce[MOCK_AUTH_NONCE_LEN];
	struct timeval tv;
	pg_crc32c	crc;

这个函数只会在初始化的时候调用一次,用来创建控制文件和初始化XLOG segment。

我们先看看第一个XLOG文件名称生成:

#define XLogFilePath(path, tli, logSegNo, wal_segsz_bytes)	\
	snprintf(path, MAXPGPATH, XLOGDIR "/%08X%08X%08X", tli,	\
			 (uint32) ((logSegNo) / XLogSegmentsPerXLogId(wal_segsz_bytes)), \
			 (uint32) ((logSegNo) % XLogSegmentsPerXLogId(wal_segsz_bytes)))

XLogSegmentsPerXLogId:

#define XLogSegmentsPerXLogId(wal_segsz_bytes)	\
	(UINT64CONST(0x100000000) / (wal_segsz_bytes))
XLogSegmentsPerXLogId = (0x100000000UL)/(1024*1024*16) = 256`
 

所以最大的XLOG文件名称为:00000001FFFFFFFF000000FF,而不是理论上的00000001FFFFFFFFFFFFFFFF,因为uint64 % 256 最大是FF。

初始化第一个timelineID = 1

/* First timeline ID is always 1 */
	ThisTimeLineID = 1;
 

还是在xlog.c这个文件中说明创建这个文件之间会先创建一个临时文件,

/*
	 * Initialize an empty (all zeroes) segment.  NOTE: it is possible that
	 * another process is doing the same thing.  If so, we will end up
	 * pre-creating an extra log segment.  That seems OK, and better than
	 * holding the lock throughout this lengthy process.
	 */
	elog(DEBUG2, "creating and filling new WAL file");

	snprintf(tmppath, MAXPGPATH, XLOGDIR "/xlogtemp.%d", (int) getpid());

pg_wal/xlogtemp.xxxxxxx("xlogtemp."加个当前进程PID),避免其他process来搞事情,然后循环写入每次写入XLOG_BLOCKSZ个字节

if (wal_init_zero)
	{
		/*
		 * Zero-fill the file.  With this setting, we do this the hard way to
		 * ensure that all the file space has really been allocated.  On
		 * platforms that allow "holes" in files, just seeking to the end
		 * doesn't allocate intermediate space.  This way, we know that we
		 * have all the space and (after the fsync below) that all the
		 * indirect blocks are down on disk.  Therefore, fdatasync(2) or
		 * O_DSYNC will be sufficient to sync future writes to the log file.
		 */
		for (nbytes = 0; nbytes < wal_segment_size; nbytes += XLOG_BLCKSZ)
		{
			errno = 0;
			if (write(fd, zbuffer.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
			{
				/* if write didn't set errno, assume no disk space */
				save_errno = errno ? errno : ENOSPC;
				break;
			}
		}
	}

写完后文件大小为16M

刚才创建的文件其实是一个空文件,随后通过durable_link_or_rename进行rename。

/*
 * durable_link_or_rename -- rename a file in a durable manner.
 *
 * Similar to durable_rename(), except that this routine tries (but does not
 * guarantee) not to overwrite the target file.
 *
 * Note that a crash in an unfortunate moment can leave you with two links to
 * the target file.
 *
 * Log errors with the caller specified severity.
 *
 * Returns 0 if the operation succeeded, -1 otherwise. Note that errno is not
 * valid upon return.
 */
int
durable_link_or_rename(const char *oldfile, const char *newfile, int elevel)
{

到这里,XLOG就创建完成了,不过这个文件还是空空如也

1.3 wal写入

1.3.1 xlog-header

首先写入的是XLOG的头(XLogPageHeader)。

/*
 * Each page of XLOG file has a header like this:
 */
#define XLOG_PAGE_MAGIC 0xD101	/* 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;

这个page_magic也可以用来验证是否是 一个page的开头

每个XLogSegment的第一个page头会写入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;

xlp_seg_size是当前xlog_segment大小=1024*1024*16;

xlp_xlog_blcksz是当前xlog的page大小=8192。结果如下:

这个page的前40个字节就是XLogLongPageHeaderData。写入完之后整个的XLOG_SEGMENT的结构如下图所示:

但是 有一个问题 就是为什么是40B?这里先按下不表,后面解析的时候会遇到问题。

1.3.2 record

日志之间有链接关系,xl_prev指向上一条日志的起始位置,下一条日志的位置用xl_tot_len可以找到,日志之间形成“双向链表”。

 

typedef struct XLogRecord
{
【日记长度】
	uint32		xl_tot_len;		/* total len of entire record */
【事务ID】
	TransactionId xl_xid;		/* xact id */
【上一条日志的LSN】
	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_info低4位保存flag信息,高4位保存日志动作信息。

XLR_INFO_MASKXLR_RMGR_INFO_MASK

    • XLR_INFO_MASK用于提取xl_info字段的低4位,该字段包含一些控制信息。
    • XLR_RMGR_INFO_MASK用于提取xl_info字段的高4位,这些位由rmgr(资源管理器)自由使用
/*
* 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.
*/
#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.
 */
#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.
 */
#define XLR_CHECK_CONSISTENCY	0x02

 

高四位:比如HEAP操作,对应8种动作信息

#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

后续解释,他会用一个很巧妙的形式找到当前的操作

数据主要分两部分:

  • 页面信息:没有实际数据,只有页面相关的信息
  • 数据信息:紧跟着XlogRecordDataHeader后面存储,
      • 实际数据<255字节,使用XLogRecordDataHeaderShort,用1字节保存数据长度
      • 否则使用XLogRecordDataHeaderLong,用4字节保存数据长度
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))

1.3.3 写入断点调试

testdb=# INSERT INTO sytest VALUES ('A');

通过发出上述语句,调用内部函数exec_simple_query()。
exec_simple_query()的伪代码如下所示:

exec_simple_query() @postgres.c

(1) ExtendCLOG() @clog.c                  写入当前事务状态"IN_PROGRESS"
(2) heap_insert()@heapam.c                当前元祖插入共享缓冲池的page
(3)   XLogInsert() @xlog.c (9.5 or later, xloginsert.c)
                                          写入LSN_1的wal缓冲区,
											将修改后的页面的pd_lsn从LSN_0更新为LSN_1。
(4) finish_xact_command() @postgres.c       
      XLogInsert() @xloginsert.c  
                                          将此记录写入LSN_2的WAL缓冲区。
(5)   XLogWrite() @xlog.c                 将WAL缓冲区上的所有XLOG记录刷新到WAL段文件。
(6) TransactionIdCommitTree() @transam.c  将此事务的状态从“IN_PROGRESS”更改为“COMMITTED”。

1.4 rmgr资源管理器

XLog日志被划分为多个类型的资源管理器,每个资源管理器只需要负责与自己相关的日志处理(抽象出操作函数,不同的日志实现不同的操作函数)。资源管理器的定义如下所示:

typedef struct RmgrData {
	const char *rm_name;     // 资源名称
	void		(*rm_redo) (XLogReaderState *record);  // 重做函数
	void		(*rm_desc) (StringInfo buf, XLogReaderState *record);  // 负责解析对应资源事务日志
	const char *(*rm_identify) (uint8 info);                           // 解析xlog记录中的Info字段
	void		(*rm_startup) (void);                                  // 启动时的初始化工作
	void		(*rm_cleanup) (void);                                  // 结束时的清理工作
	void		(*rm_mask) (char *pagedata, BlockNumber blkno);        // 在对页面做一致性检查前,是否需要对页面的某些部分做掩码
} RmgrData;
extern const RmgrData RmgrTable[];

据xlog中的xl_rmid调用资源管理器中不同资源的rm_redo回放函数进行回放。

/* symbol name, textual name, redo, desc, identify, startup, cleanup */
PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, xlog_identify, NULL, NULL, NULL)
PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, xact_identify, NULL, NULL, NULL)
PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, smgr_identify, NULL, NULL, NULL)
PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, clog_identify, NULL, NULL, NULL)
PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, dbase_identify, NULL, NULL, NULL)
PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, tblspc_identify, NULL, NULL, NULL)
PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, multixact_identify, NULL, NULL, NULL)
PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, relmap_identify, NULL, NULL, NULL)
PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, standby_identify, NULL, NULL, NULL)
PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, heap2_identify, NULL, NULL, heap_mask)
PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, heap_identify, NULL, NULL, heap_mask)
PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, NULL, NULL, btree_mask)
PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL, hash_mask)
PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup, gin_mask)
PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup, gist_mask)
PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL, seq_mask)
PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup, spg_mask)
PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL, brin_mask)
PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL, NULL)
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL, generic_mask)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL, NULL)

在 PostgreSQL 内核中,rmgrlist.h 文件定义了各种类型的 rmgr(record manager),每个 rmgr 对应一个不同的数据库操作。这些 rmgr 在事务日志(XLog)中起着重要作用,用于记录不同类型的数据库操作,以便在之后的恢复过程中重做(redo)或撤销(undo)这些操作。

  1. RM_XLOG_ID: 这是 XLog 自身的 rmgr,负责记录 XLog 中的操作,用于数据库的恢复和故障转移。
  2. RM_XACT_ID: 这个 rmgr 负责记录事务提交和回滚的操作,用于数据库的一致性和事务管理。
  3. RM_SMGR_ID: 这个 rmgr 负责记录存储管理器(SMGR)相关的操作,用于数据库的物理存储管理。
  4. RM_CLOG_ID: 这个 rmgr 负责记录提交日志(CLOG)相关的操作,用于事务的可见性和并发控制。
  5. RM_DBASE_ID: 这个 rmgr 负责记录数据库的创建和删除操作。
  6. RM_TBLSPC_ID: 这个 rmgr 负责记录表空间的创建和删除操作。
  7. RM_MULTIXACT_ID: 这个 rmgr 负责记录多版本并发控制(MVCC)相关的操作。
  8. RM_RELMAP_ID: 这个 rmgr 负责记录关系映射(RelMap)相关的操作。
  9. RM_STANDBY_ID: 这个 rmgr 负责记录热备相关的操作。
  10. RM_HEAP2_ID: 这个 rmgr 也负责记录堆表(Heap)的修改操作。
  11. RM_HEAP_ID: 这个 rmgr 负责记录堆表(Heap)的修改操作,包括插入、更新和删除等。
  12. RM_BTREE_ID: 这个 rmgr 负责记录 B 树索引的修改操作。
  13. RM_HASH_ID: 这个 rmgr 负责记录哈希索引的修改操作。
  14. RM_GIN_ID: 这个 rmgr 负责记录 GIN 索引的修改操作。
  15. RM_GIST_ID: 这个 rmgr 负责记录 GIST 索引的修改操作。
  16. RM_SEQ_ID: 这个 rmgr 负责记录序列(Sequence)的修改操作。
  17. RM_SPGIST_ID: 这个 rmgr 负责记录 SP-GiST 索引的修改操作。
  18. RM_BRIN_ID: 这个 rmgr 负责记录 BRIN 索引的修改操作。
  19. RM_COMMIT_TS_ID: 这个 rmgr 负责记录提交时间戳的修改操作。
  20. RM_REPLORIGIN_ID: 这个 rmgr 负责记录复制源的修改操作。
  21. RM_GENERIC_ID: 这个 rmgr 用于实验性目的,可用于自定义类型的操作。
  22. RM_LOGICALMSG_ID: 这个 rmgr 用于逻辑复制相关的操作。
  • symname: 资源管理器的枚举类型名称。
  • name: 资源管理器的名称,通常与rmgr的功能相关。
  • redo: 资源管理器的重做函数,用于在数据库恢复时重做操作。
  • desc: 资源管理器的描述,对资源管理器进行简要说明。
  • identify: 资源管理器的识别函数,用于识别给定XLog记录类型所属的资源管理器。
  • startup: 资源管理器的启动函数,在数据库启动时执行。
  • cleanup: 资源管理器的清理函数,在数据库关闭时执行。
  • mask: 资源管理器的掩码,用于标识资源管理器是否支持某些特定功能。

heap_mask 函数是 PostgreSQL 内核中用于在执行一致性检查之前对堆页(heap page)进行掩码处理的函数。在执行恢复操作时,数据库会读取 XLog 记录中的内容,并应用到对应的数据页上。为了保证恢复的正确性,必须对数据页进行一致性检查,而 heap_mask 就是用于在执行这些检查之前,对数据页进行掩码处理,从而屏蔽掉不应该参与一致性检查的部分。

以下是 heap_mask 函数的主要功能:

  1. mask_page_lsn_and_checksum:掩码处理数据页的 LSN(Log Sequence Number)和校验和字段,这些字段在数据页恢复时不应该被检查。
  2. mask_page_hint_bits:掩码处理数据页的提示位(hint bits),这些位可能在恢复过程中有变化,但不影响数据页的一致性。
  3. mask_unused_space:掩码处理数据页的未使用空间,这些空间在一致性检查中不应该被考虑。
  4. 对数据页中的每个条目进行处理:
    • 如果是普通的堆元组(HeapTuple),则对其中的某些字段进行掩码,例如事务标识(xmin、xmax)和命令标识(cid)等。在恢复过程中,这些字段可能会有变化,但不影响数据页的一致性。
    • 忽略特定情况下的堆元组的“ctid”字段,用于支持 speculative insertion,这是一种特殊的插入方式。
    • 忽略堆元组中不满足 MAXALIGN 对齐要求的填充字节。

总的来说,heap_mask 函数的目的是为了屏蔽掉在恢复过程中可能出现的无关变化,保证数据页在进行一致性检查时具有稳定的状态。这样可以确保数据恢复的正确性,并且避免因为恢复过程中的变化导致一致性检查失败。

那为什么 有的有mask 有的没有mask?

RMGR可以分为两类:

  1. 有Mask Function的RMGR:
    • 这些RMGR处理的是数据表(如堆表、B树索引、哈希索引、GIN索引等)的重做操作。
    • 在进行WAL记录的重做时,可能会存在一些可允许的差异,例如提示信息、标记位等,这些差异不会影响数据的一致性,因此需要使用Mask Function将这些差异屏蔽掉,只关注可能导致数据不一致的重要内容。
    • 使用Mask Function可以提高一致性检查的灵活性,减少误报,同时确保数据的准确性。
  1. 没有Mask Function的RMGR:
    • 这些RMGR处理的是一些不需要考虑差异的重做操作,或者重做操作本身就足够保证数据的一致性。
    • 比如XLOG记录的重做、事务的重做、存储管理器的重做等,这些操作在重做时不需要考虑额外的Mask操作,因为它们本身已经具有足够的机制来保证数据的正确性。

我自己的理解:

在实现WAL一致性检查功能时,只有涉及数据表的RMGR才需要使用Mask Function,而对于其他类型的RMGR,由于其重做操作本身已经保证了数据的正确性,因此不需要添加Mask Function。

通过区分需要Mask的RMGR和不需要Mask的RMGR,可以在WAL一致性检查过程中准确地找出可能导致数据不一致的地方,并对数据库的一致性进行严格的验证。这样,复制过程中出现的潜在问题可以及早被检测出来,并及时处理,保障数据库系统的可靠性和稳定性。

2.解析wal日志

结果展示

-----------------LONGHEADER------------------------
xlp_magic: D101
xlp_info: 7
xlp_tli: 1
xlp_pageaddr: 77000000
xlp_rem_len: 1257
xlp_sysid: 64B6A0F56CFBE0AA
xlp_seg_size: 1000000
xlp_seg_size in MB: 16.0
xlp_xlog_blcksz: 2000
xlp_xlog_blcksz in KB: 8.0
---------------RECORD-----------------------
xl_tot_len: F5
xl_xid: A048
xl_prev: 76FFF238
xl_info: 0
xl_rmid: A
xl_crc: AA2568A1
---------------INFO-----------------------
当前info:  0
current opera --> insert
---------------XLogRecordBlockHeader-----------------------
id: 00
fork_flags: 20
data_length: 00C4
ref.spcNode: 0000067F
ref.dbNode: 0000401C
ref.relNode: 000040FE
blockNumber: 0000151D
---------------XLogRecordDataHeaderShort-----------------------
id: 255
data_length: 3
---------------xl_heap_header-----------------------
t_infomask2: 4
t_infomask: 2050
t_hoff: 24

 

2.1 结构定义

import ctypes
import sys
class XlogPageHeaderDate(ctypes.Structure):
    _fields_ = [
        ('xlp_magic',ctypes.c_uint16),
        ('xlp_info',ctypes.c_uint16),
        ('xlp_tli',ctypes.c_uint32),
        ('xlp_pageaddr',ctypes.c_uint64),
        ('xlp_rem_len',ctypes.c_uint32),
        ('xlp_al',ctypes.c_uint32) #用于对齐的4个byte
    ]
class XLogLongPageHeaderData(ctypes.Structure):
    _fields_ = [
        ('std', XlogPageHeaderDate),
        ('xlp_sysid', ctypes.c_uint64),
        ('xlp_seg_size', ctypes.c_uint32),
        ('xlp_xlog_blcksz', ctypes.c_uint32)
    ]

class XLogRecord(ctypes.Structure):
    _fields_ = [
        ('xl_tot_len', ctypes.c_uint32),
        ('xl_xid', ctypes.c_uint32),
        ('xl_prev', ctypes.c_uint64),
        ('xl_info', ctypes.c_uint8),
        ('xl_rmid', ctypes.c_uint8),
        ('_padding', ctypes.c_uint16),  # 2 bytes of padding, initialize to zero
        ('xl_crc', ctypes.c_uint32),
    ]

class RelFileNode(ctypes.Structure):  
    _fields_ = [  
        ('spcNode', ctypes.c_uint32),  
        ('dbNode', ctypes.c_uint32),  
        ('relNode', ctypes.c_uint32),  
    ]

class XLogRecordBlockHeader(ctypes.Structure):  
    _fields_ = [  
        ('id', ctypes.c_uint8),  
        ('fork_flags', ctypes.c_uint8),  
        ('data_length', ctypes.c_uint16), 
        ('ref',  RelFileNode),
        ('block_num', ctypes.c_int32),
    ]

class XLogRecordDataHeaderShort(ctypes.Structure):
    _fields_ = [
        ('id', ctypes.c_uint8),
        ('data_length', ctypes.c_uint8),
    ]

class XLHeapHeader(ctypes.Structure):
    _fields_ = [
        ('t_infomask2', ctypes.c_uint16),
        ('t_infomask', ctypes.c_uint16),
        ('t_hoff',ctypes.c_ubyte)
        # ('t_hoff', ctypes.c_uint8),
    ]

问题一 为什么XLogLongPageHeaderData 一搜都是40B ?

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 */--32
	XLogRecPtr	xlp_pageaddr;	/* XLOG address of this page */--64
	uint32		xlp_rem_len;	/* total len of remaining data for record */
} XLogPageHeaderData;

那这一段加起来应该是 2+2+4+8+4 = 20B

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;

这一段应该是 20+8+4+4 = 36 也不应该是40啊?

仔细阅读源码,发现问题

在XLogPageHeaderData底下出现了这个定义

#define SizeOfXLogShortPHD	MAXALIGN(sizeof(XLogPageHeaderData))

这一块定义了XLogPageHeaderData的长度

分析一下 MAXALIGN---其实读英文名就可以知道 他的意思是对齐 最大

#define MAXALIGN(LEN)			TYPEALIGN(MAXIMUM_ALIGNOF, (LEN))

简单看来 是一个求取长度的内容

/* ----------------
 * Alignment macros: align a length or address appropriately for a given type.
 * The fooALIGN() macros round up to a multiple of the required alignment,
 * while the fooALIGN_DOWN() macros round down.  The latter are more useful
 * for problems like "how many X-sized structures will fit in a page?".
 *
 * NOTE: TYPEALIGN[_DOWN] will not work if ALIGNVAL is not a power of 2.
 * That case seems extremely unlikely to be needed in practice, however.
 *
 * NOTE: MAXIMUM_ALIGNOF, and hence MAXALIGN(), intentionally exclude any
 * larger-than-8-byte types the compiler might have.
 * ----------------
 */

这个上面的内容就是 PG内部做了一个对齐宏,类似于 只要是PG内部的东西 基本上就按照这个对齐宏走

其中 MAXIMUM_ALIGNOF 这个是获得当前编译器中占最大位的基本类型,比如long long类型

取得的long long类型的大小为8BYTE,所以在这个宏定义当中,MAXIMUM_ALIGNOF=8

#define TYPEALIGN(ALIGNVAL,LEN)  \
	(((uintptr_t) (LEN) + ((ALIGNVAL) - 1)) & ~((uintptr_t) ((ALIGNVAL) - 1)))
  1. ALIGNVAL = 8 = 1000
  2. ~((uintptr_t) ((ALIGNVAL) - 1)) = 1000
  3. 前面的部分(((uintptr_t) (LEN) + ((ALIGNVAL) - 1))就是20+7 =27 ,转化为二进制为 0001 1011。
  4. 与第(3)步的计算值进行&运算,得到0001 1000,转换为十进制为24。所以最初的宏SizeOfXLogLongPHD的大小为24

MAXALIGN 的作用其实就是为了使sizeof( struct )向上对齐,成为8的倍数的大小

2.2 逻辑写入位置与物理写入位置

思考为什么会有rem等问题,如果这个操作的tuple内容很多怎么办,或者当前record没有填满怎么办

按道理来说,他的存储应该排列整齐的样子

static void
ReserveXLogInsertLocation(int size, XLogRecPtr *StartPos, XLogRecPtr *EndPos,
						  XLogRecPtr *PrevPtr)
{
	XLogCtlInsert *Insert = &XLogCtl->Insert;
	uint64		startbytepos;
	uint64		endbytepos;
	uint64		prevbytepos;

	size = MAXALIGN(size);
  
  ...
    
	SpinLockAcquire(&Insert->insertpos_lck);

	startbytepos = Insert->CurrBytePos;
	endbytepos = startbytepos + size;
	prevbytepos = Insert->PrevBytePos;
	Insert->CurrBytePos = endbytepos;
	Insert->PrevBytePos = startbytepos;

	SpinLockRelease(&Insert->insertpos_lck);

	*StartPos = XLogBytePosToRecPtr(startbytepos);
	*EndPos = XLogBytePosToEndRecPtr(endbytepos);
	*PrevPtr = XLogBytePosToRecPtr(prevbytepos);

  ...
}

 

#define UsableBytesInPage (XLOG_BLCKSZ - SizeOfXLogShortPHD)
#define UsableBytesInSegment ((XLOG_SEG_SIZE / XLOG_BLCKSZ) * UsableBytesInPage - (SizeOfXLogLongPHD - SizeOfXLogShortPHD))

// UsableBytesInSegment = (XLOG_SEG_SIZE / XLOG_BLCKSZ) * UsableBytesInPage - (SizeOfXLogLongPHD - SizeOfXLogShortPHD)
// UsableBytesInSegment = (16 * 1024 * 1024 / 8192) * (8192 - SizeOfXLogShortPHD) - (XlogHeaderDiff)
// UsableBytesInSegment = 页面数量 * 每个页面除了HEADER之外的空间 - 第一个页面的HEADER多出来的一部分
// UsableBytesInSegment = 一个SEG内能用来保存XLOG的空间总大小
// UsableBytesInSegment = 16728048
// 如果没有HEADER,UsableBytesInSegment = 16 * 1024 * 1024 = 16777216

 他会根据逻辑地址算出物理插入地址,也就是说,物理地址并不会像逻辑地址那样整齐排列。

2.3判断rmgr操作类型

拿到对应的info后,应该怎么判断它属于什么操作,增删改查?

uint8				info;
	
	info = XLogRecGetInfo(record) & ~XLR_INFO_MASK;
	info &= XLOG_HEAP_OPMASK;
	
	if (XLOG_HEAP_INSERT == info)
	{
		插入
	}
	else if(XLOG_HEAP_DELETE == info)
	{
		删除
	}
	else if (XLOG_HEAP_UPDATE == info)
	{
		更新
	}
	else if (XLOG_HEAP_HOT_UPDATE == info)
	{
		更新
	}
def judge_info(info_num):
    info = info_num & ~XLR_INFO_MASK
    info &= XLOG_HEAP_OPMASK

    if XLOG_HEAP_INSERT == info:
        print('current opera --> insert')
    elif XLOG_HEAP_DELETE == info:
        print('current opera --> delete')
    elif XLOG_HEAP_UPDATE == info or XLOG_HEAP_HOT_UPDATE == info:
        print('current opera --> update')
# rmid定义判断
RM_XLOG_ID = 0x00
RM_XACT_ID = 0x01
RM_SMGR_ID = 0x02
RM_CLOG_ID = 0x03
RM_DBASE_ID = 0x04
RM_TBLSPC_ID = 0x05
RM_MULTIXACT_ID = 0x06
RM_RELMAP_ID = 0x07
RM_STANDBY_ID = 0x08
RM_HEAP2_ID = 0x09
RM_HEAP_ID = 0x0A
RM_BTREE_ID = 0x0B
RM_HASH_ID = 0x0C
RM_GIN_ID = 0x0D
RM_GIST_ID = 0x0E
RM_SEQ_ID = 0x0F
RM_SPGIST_ID = 0x10
RM_BRIN_ID = 0x11
RM_COMMIT_TS_ID = 0x12
RM_REPLORIGIN_ID = 0x13
RM_GENERIC_ID = 0x14
RM_LOGICALMSG_ID = 0x15

2.4 读取wal

def read_wal(path):
    with open(path,mode='rb') as f:
        buf = f.read(XLOG_BLCKSZ)
        # print(buf)
        hdr = buf[:XLOG_HDRLEN]
        # hdr = buf
        lhdr = buf[:XLOG_LONGHDRLEN]
        rem = buf[XLOG_LONGHDRLEN:XLOG_REM+XLOG_LONGHDRLEN] # 看看是不是page的第一个!!!
        record = buf[XLOG_REM+XLOG_LONGHDRLEN : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD]
        rbh = buf[XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER]
        rdhs = buf[XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER 
                  : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER + XLOGRECORDDATAHEADERSHORT]
        xhh = buf[XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER + XLOGRECORDDATAHEADERSHORT 
                  : XLOG_REM+XLOG_LONGHDRLEN + XLOG_RECORD + XLOGRECORDBLOCKHEADER + XLOGRECORDDATAHEADERSHORT + XLOGHEAPHEADER +1]

 

文章来自个人专栏
wal解析
1 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0