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_MASK
和XLR_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)这些操作。
RM_XLOG_ID
: 这是 XLog 自身的 rmgr,负责记录 XLog 中的操作,用于数据库的恢复和故障转移。RM_XACT_ID
: 这个 rmgr 负责记录事务提交和回滚的操作,用于数据库的一致性和事务管理。RM_SMGR_ID
: 这个 rmgr 负责记录存储管理器(SMGR)相关的操作,用于数据库的物理存储管理。RM_CLOG_ID
: 这个 rmgr 负责记录提交日志(CLOG)相关的操作,用于事务的可见性和并发控制。RM_DBASE_ID
: 这个 rmgr 负责记录数据库的创建和删除操作。RM_TBLSPC_ID
: 这个 rmgr 负责记录表空间的创建和删除操作。RM_MULTIXACT_ID
: 这个 rmgr 负责记录多版本并发控制(MVCC)相关的操作。RM_RELMAP_ID
: 这个 rmgr 负责记录关系映射(RelMap)相关的操作。RM_STANDBY_ID
: 这个 rmgr 负责记录热备相关的操作。RM_HEAP2_ID
: 这个 rmgr 也负责记录堆表(Heap)的修改操作。RM_HEAP_ID
: 这个 rmgr 负责记录堆表(Heap)的修改操作,包括插入、更新和删除等。RM_BTREE_ID
: 这个 rmgr 负责记录 B 树索引的修改操作。RM_HASH_ID
: 这个 rmgr 负责记录哈希索引的修改操作。RM_GIN_ID
: 这个 rmgr 负责记录 GIN 索引的修改操作。RM_GIST_ID
: 这个 rmgr 负责记录 GIST 索引的修改操作。RM_SEQ_ID
: 这个 rmgr 负责记录序列(Sequence)的修改操作。RM_SPGIST_ID
: 这个 rmgr 负责记录 SP-GiST 索引的修改操作。RM_BRIN_ID
: 这个 rmgr 负责记录 BRIN 索引的修改操作。RM_COMMIT_TS_ID
: 这个 rmgr 负责记录提交时间戳的修改操作。RM_REPLORIGIN_ID
: 这个 rmgr 负责记录复制源的修改操作。RM_GENERIC_ID
: 这个 rmgr 用于实验性目的,可用于自定义类型的操作。RM_LOGICALMSG_ID
: 这个 rmgr 用于逻辑复制相关的操作。
symname
: 资源管理器的枚举类型名称。name
: 资源管理器的名称,通常与rmgr的功能相关。redo
: 资源管理器的重做函数,用于在数据库恢复时重做操作。desc
: 资源管理器的描述,对资源管理器进行简要说明。identify
: 资源管理器的识别函数,用于识别给定XLog记录类型所属的资源管理器。startup
: 资源管理器的启动函数,在数据库启动时执行。cleanup
: 资源管理器的清理函数,在数据库关闭时执行。mask
: 资源管理器的掩码,用于标识资源管理器是否支持某些特定功能。
heap_mask
函数是 PostgreSQL 内核中用于在执行一致性检查之前对堆页(heap page)进行掩码处理的函数。在执行恢复操作时,数据库会读取 XLog 记录中的内容,并应用到对应的数据页上。为了保证恢复的正确性,必须对数据页进行一致性检查,而 heap_mask
就是用于在执行这些检查之前,对数据页进行掩码处理,从而屏蔽掉不应该参与一致性检查的部分。
以下是 heap_mask
函数的主要功能:
mask_page_lsn_and_checksum
:掩码处理数据页的 LSN(Log Sequence Number)和校验和字段,这些字段在数据页恢复时不应该被检查。mask_page_hint_bits
:掩码处理数据页的提示位(hint bits),这些位可能在恢复过程中有变化,但不影响数据页的一致性。mask_unused_space
:掩码处理数据页的未使用空间,这些空间在一致性检查中不应该被考虑。- 对数据页中的每个条目进行处理:
- 如果是普通的堆元组(HeapTuple),则对其中的某些字段进行掩码,例如事务标识(xmin、xmax)和命令标识(cid)等。在恢复过程中,这些字段可能会有变化,但不影响数据页的一致性。
- 忽略特定情况下的堆元组的“ctid”字段,用于支持 speculative insertion,这是一种特殊的插入方式。
- 忽略堆元组中不满足 MAXALIGN 对齐要求的填充字节。
总的来说,heap_mask
函数的目的是为了屏蔽掉在恢复过程中可能出现的无关变化,保证数据页在进行一致性检查时具有稳定的状态。这样可以确保数据恢复的正确性,并且避免因为恢复过程中的变化导致一致性检查失败。
那为什么 有的有mask 有的没有mask?
RMGR可以分为两类:
- 有Mask Function的RMGR:
- 这些RMGR处理的是数据表(如堆表、B树索引、哈希索引、GIN索引等)的重做操作。
- 在进行WAL记录的重做时,可能会存在一些可允许的差异,例如提示信息、标记位等,这些差异不会影响数据的一致性,因此需要使用Mask Function将这些差异屏蔽掉,只关注可能导致数据不一致的重要内容。
- 使用Mask Function可以提高一致性检查的灵活性,减少误报,同时确保数据的准确性。
- 没有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)))
- ALIGNVAL = 8 = 1000
- ~((uintptr_t) ((ALIGNVAL) - 1)) = 1000
- 前面的部分(((uintptr_t) (LEN) + ((ALIGNVAL) - 1))就是20+7 =27 ,转化为二进制为 0001 1011。
- 与第(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]