pg中wal子系统的存在是为了故障恢复,它也被用于基于时间点的恢复、基于日志搬迁的Hot-standby复制。以下想描述一点wal日志的设计理念。
wal日志的一个基本假设是日志条目必须先于它所描述的数据变化页面持久化到稳定存储(如硬盘)。这确保重放日志到它的末端将使数据库可以重新达到一致性状态(不存在部分执行的事务)。为了达到这一点,每一个数据页面(堆或索引页面)被标记上了影响本页面的最新xlog记录的LSN号。
LSN号,全称Log sequence number, 实际上代表wal文件位置。
在wal重放时,可以核对页面的LSN来确定本条xlog记录是否已经被重放(如果页面LSN>日志条目的位置,则该条日志已被重放)。
xlog详细记录了服务进程对数据库的操作过程。xlog日志文件在内存中按页存放,每个页面大小为8kb,每个页面都有一个头部,头部信息之后才是xlog日志记录。
每个xlog文件都有一个ID,但事实上它被分成一个个固定大小(默认16MB,initdb时可由–wal-segsize指定)的XLOG段文件来存放。xlog文件号和段文件号可以用来唯一确定这个段文件。确定日志文件内一个日志记录的地址时,只需用一个XLOG文件号和日志记录在该文件内的偏移量即可。
initdb初始化data时,在函数bootstrap_template1中初始化template1模板库时,通过popen调用postgres程序中的AuxiliaryProcessMain,AuxiliaryProcessMain中调用BootStrapXLOG函数完成XLOG段文件初始化工作。
initdb生成xlog文件时,用以下宏生成文件名,
#define XLogFilePath(path, tli, logSegNo) \
snprintf(path, MAXPGPATH, XLOGDIR "/%08X%08X%08X", tli, \
(uint32) ((logSegNo) / XLogSegmentsPerXLogId), \
(uint32) ((logSegNo) % XLogSegmentsPerXLogId))
#define XLOG_SEG_SIZE (16 * 1024 * 1024)
#define XLogSegmentsPerXLogId (UINT64CONST(0x100000000) / XLOG_SEG_SIZE)
note: XLogSegmentsPerXlogId值为256,XLOG_SEG_SIZE为段大小16MB
宏中变量:
path: 代表文件名
tli:代表时间线,初始时间线为1
logSegNo:代表段号,初始值为1
以上值代入宏,经snprint格式化后,path值为:
pg_xlog路径+时间线+(uint32)段号/256+段号%256 --%08X代表按8位16进制数显示
结果即:
pg_xlog/+00000001+00000000+00000001
Note: 此处,可以看出段文件名最后两位最大值为256,转为16进制则段文件名的最后8位最大为000000FF.
每一个XLOG文件有一个ID,事实上一个逻辑上的xlog文件物理上被分割为一个个固定大小的段(默认16MB)来保存。xlog文件号和段号可以唯一确定这个段文件。确定日志文件内的一个日志记录的地址时,只需用xlog文件号和日志记录在该文件内的偏移量即可。
对于每一个Xlog文件第一个段的第一个页面是一个长头部(Long Header),一个xlog文件头部是不是长头部,可以由其头部XLogPageHeaderData的标志位xlp_info确定出来,如果是长头部,则:
XLogPageHeader page;
page->xlp_info = XLP_LONG_HEADER;
1)xlog日志页面头部信息
xlog日志文件分为许多的逻辑段,每一个段文件又分成许多个页面,每一个页面大小为一个块的大小。对于每一个日志页面,需要在其头部写一个头部信息XLogPageHeaderData,其结构如下:
/*
* Each page of XLOG file has a header like this:
*/
#define XLOG_PAGE_MAGIC 0xD093 /* 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;
#define SizeOfXLogShortPHD MAXALIGN(sizeof(XLogPageHeaderData))
typedef XLogPageHeaderData *XLogPageHeader;
如果一个页面是一个逻辑段文件的第一个页面,那么在页面头部信息标志位会设置XLP_LONG_HEADER标记,那么将在原页面头部信息的基础上使用一个长的XLOG页面头部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;
#define SizeOfXLogLongPHD MAXALIGN(sizeof(XLogLongPageHeaderData))
typedef XLogLongPageHeaderData *XLogLongPageHeader;
2)xlog日志记录结构信息
XLOG Record由两部分组成,第一部分是XLOG Record的头部信息,大小固定(24 Bytes),对应的结构体是XLogRecord;第二部分是XLOG Record data。
xlog记录存储格局:
Fixed-size header (XLogRecord struct)
XLogRecordBlockHeader struct
XLogRecordBlockHeader struct
…
XLogRecordDataHeader[Short|Long] struct
block data
block data
…
main data
XLOG Record按存储的数据内容来划分,大体可以分为三类:
Record for backup block:存储full-write-page的block,这种类型Record是为了解决page部分写的问题。在checkpoint完成后第一次修改数据page,在记录此变更写入事务日志文件时整页写入(需设置参数full_page_write,默认为打开);
Record for tuple data block:存储page中的tuple变更,使用这种类型的Record记录;
Record for Checkpoint:在checkpoint发生时,在事务日志文件中记录checkpoint信息。
XLogRecord记录了Xlog记录的相关控制信息,
typedef struct XLogRecord
{
uint32 xl_tot_len; /* total len of entire record */
TransactionId xl_xid; /* xact id */
XLogRecPtr xl_prev; /* ptr to previous record in log */
uint8 xl_info; /* flag bits, see below */
RmgrId xl_rmid; /* resource manager for this record */
/* 2 bytes of padding here, initialize to zero */
pg_crc32c xl_crc; /* CRC for this record */
/* XLogRecordBlockHeaders and XLogRecordDataHeader follow, no padding */
} XLogRecord;
xl_tot_len //整条记录总长度
xl_xid //事务ID
xl_prev //在日志中的前一条记录
xl_info //信息标志位
xl_rmid //资源管理器ID
其中,xl_info被资源管理器使用,表示该日志是哪种类型的日志,其取值如下:
/* XLOG info values for XLOG rmgr */
#define XLOG_CHECKPOINT_SHUTDOWN 0x00
#define XLOG_CHECKPOINT_ONLINE 0x10
#define XLOG_NOOP 0x20
#define XLOG_NEXTOID 0x30
#define XLOG_SWITCH 0x40
#define XLOG_BACKUP_END 0x50
#define XLOG_PARAMETER_CHANGE 0x60
#define XLOG_RESTORE_POINT 0x70
#define XLOG_FPW_CHANGE 0x80
#define XLOG_END_OF_RECOVERY 0x90
#define XLOG_FPI_FOR_HINT 0xA0
#define XLOG_FPI 0xB0
其中,xl_rmid资源管理器的取值如下:
/* symbol name, textual name, redo, desc, identify, startup, cleanup */
PG_RMGR(RM_XLOG_ID, "XLOG", xlog_redo, xlog_desc, xlog_identify, NULL, NULL)
PG_RMGR(RM_XACT_ID, "Transaction", xact_redo, xact_desc, xact_identify, NULL, NULL)
PG_RMGR(RM_SMGR_ID, "Storage", smgr_redo, smgr_desc, smgr_identify, NULL, NULL)
PG_RMGR(RM_CLOG_ID, "CLOG", clog_redo, clog_desc, clog_identify, NULL, NULL)
PG_RMGR(RM_DBASE_ID, "Database", dbase_redo, dbase_desc, dbase_identify, NULL, NULL)
PG_RMGR(RM_TBLSPC_ID, "Tablespace", tblspc_redo, tblspc_desc, tblspc_identify, NULL, NULL)
PG_RMGR(RM_MULTIXACT_ID, "MultiXact", multixact_redo, multixact_desc, multixact_identify, NULL, NULL)
PG_RMGR(RM_RELMAP_ID, "RelMap", relmap_redo, relmap_desc, relmap_identify, NULL, NULL)
PG_RMGR(RM_STANDBY_ID, "Standby", standby_redo, standby_desc, standby_identify, NULL, NULL)
PG_RMGR(RM_HEAP2_ID, "Heap2", heap2_redo, heap2_desc, heap2_identify, NULL, NULL)
PG_RMGR(RM_HEAP_ID, "Heap", heap_redo, heap_desc, heap_identify, NULL, NULL)
PG_RMGR(RM_BTREE_ID, "Btree", btree_redo, btree_desc, btree_identify, NULL, NULL)
PG_RMGR(RM_HASH_ID, "Hash", hash_redo, hash_desc, hash_identify, NULL, NULL)
PG_RMGR(RM_GIN_ID, "Gin", gin_redo, gin_desc, gin_identify, gin_xlog_startup, gin_xlog_cleanup)
PG_RMGR(RM_GIST_ID, "Gist", gist_redo, gist_desc, gist_identify, gist_xlog_startup, gist_xlog_cleanup)
PG_RMGR(RM_SEQ_ID, "Sequence", seq_redo, seq_desc, seq_identify, NULL, NULL)
PG_RMGR(RM_SPGIST_ID, "SPGist", spg_redo, spg_desc, spg_identify, spg_xlog_startup, spg_xlog_cleanup)
PG_RMGR(RM_BRIN_ID, "BRIN", brin_redo, brin_desc, brin_identify, NULL, NULL)
PG_RMGR(RM_COMMIT_TS_ID, "CommitTs", commit_ts_redo, commit_ts_desc, commit_ts_identify, NULL, NULL)
PG_RMGR(RM_REPLORIGIN_ID, "ReplicationOrigin", replorigin_redo, replorigin_desc, replorigin_identify, NULL, NULL)
PG_RMGR(RM_GENERIC_ID, "Generic", generic_redo, generic_desc, generic_identify, NULL, NULL)
PG_RMGR(RM_LOGICALMSG_ID, "LogicalMessage", logicalmsg_redo, logicalmsg_desc, logicalmsg_identify, NULL, NULL)
下列出其中较重要几种资源管理器ID含义:
RM_XLOG_ID:该条日志记录的是一个检查点信息
RM_XACT_ID:该条日志记录的是一个事务的提交或终止信息
RM_CLOG_ID:该条日志记录的是CLOG中某一页的初始化
RM_HEAP_ID:该条日志记录的是对堆中元组进行修改的信息
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等日志数据.
。。。
细节内容有点多,本篇先写到这里