postgresql源码学习(22)—— 事务日志③-日志的注册

67 篇文章 51 订阅
34 篇文章 3 订阅

一、 日志写入的整体流程

从功能上看

从对应函数来看

本节主要介绍日志注册(蓝色)部分。

二、 主要结构体与变量

1. registered_buffer结构体与registered_buffers数组

       这俩名字很像,registered_buffers是一个数组,其中的每个元素由registered_buffer结构体组成。该数组用于注册被修改的页面信息,XLogRegisterBuffer函数每注册一个页面时,会在其中占用一个槽位。

/*
 * For each block reference registered with XLogRegisterBuffer, we fill in
 * a registered_buffer struct.
 */
typedef struct
{
	bool		in_use;			/* is this slot in use? 该槽位是否在使用? */
	uint8		flags;			/* REGBUF_* flags,buffer相关的标记位,参考下方 */
	RelFileNode rnode;			/* identifies the relation and block,表对应的块物理信息 */
	ForkNumber	forkno;
	BlockNumber block;
	Page		page;			/* page content,页面内容指针 */
	uint32		rdata_len;		/* total length of data in rdata chain,rdata链表(由XLogRegisterBufData函数注册,下面会介绍)的总长度 */
	XLogRecData *rdata_head;	/* head of the chain of data registered with this block,rdata链表的第一个块*/
	XLogRecData *rdata_tail;	/* last entry in the chain, or &rdata_head if empty,,rdata链表的最后一个块*/

	XLogRecData bkp_rdatas[2];	/* temporary rdatas used to hold references to backup block data in XLogRecordAssemble(),XLogRecordAssemble函数不方便再调用XLogRegister*函数,这里设定两个临时变量 */

	/* buffer to store a compressed version of backup block image,存储备份区块镜像压缩版本信息的buffer */
	char		compressed_page[PGLZ_MAX_BLCKSZ];
} registered_buffer;

static registered_buffer *registered_buffers;  // 定义registered_buffers数组
static int	max_registered_buffers; /* allocated size,数组最大空间 */
static int	max_registered_block_id = 0;	/* highest block_id + 1 currently registered,当前已注册的最大块号+1 */

2. XLogRecData结构体与rdatas数组

      rdatas是一个数组,其中的每个元素由XLogRecData结构体组成。事务日志并不直接写入WAL Buffer,而是先将XLogRecData组成链表,然后将此链表转为一条事务日志。

/*
 * The functions in xloginsert.c construct a chain of XLogRecData structs
 * to represent the final WAL record.位于xlog_internal.h
 */
typedef struct XLogRecData
{
	struct XLogRecData *next;	/* next struct in chain, or NULL,指向链表中下一个元素 */
	char	   *data;			/* start of rmgr data to include,rmgr data起始位置 */
	uint32		len;			/* length of rmgr data to include,rmgr data长度 */
} XLogRecData;

/*
 * An array of XLogRecData structs, to hold registered data.位于xloginsert.c
 */
static XLogRecData *rdatas;
static int	num_rdatas;			/* entries currently used */
static int	max_rdatas;			/* allocated size */

两个结构体和数组关系如下: 

三、 起始函数 XLogBeginInsert

这个函数主要干两件事:

  • 通过设置begininsert_called标志,防止递归调用日志生成函数
  • 通过XLogInsertAllowed函数和一些Assert做代码安全检查工作
/*
 * Begin constructing a WAL record. This must be called before the
 * XLogRegister* functions and XLogInsert().
 */
void
XLogBeginInsert(void)
{
	Assert(max_registered_block_id == 0);
	Assert(mainrdata_last == (XLogRecData *) &mainrdata_head);
	Assert(mainrdata_len == 0);

	/* cross-check on whether we should be here or not,恢复期间不能执行WAL插入 */
	if (!XLogInsertAllowed())
		elog(ERROR, "cannot make new WAL entries during recovery");

	/* 避免写日志的时候又要生成日志进行插入,陷入死循环 */
	if (begininsert_called)
		elog(ERROR, "XLogBeginInsert was already called");

	/* 标记位设置 */
	begininsert_called = true;
}

四、 注册函数

主要是以下3个函数(都在xloginsert.c文件中):

  • XLogRegisterData:将WAL日志数据注册到rdatas数组
  • XLogRegisterBuffer:将被修改的buffer页面信息注册到registerd_buffers数组。还有一个类似的函数XLogRegisterBlock,负责注册不在shared buffer pool中的块。
  • XLogRegisterBufData:将rdatas数组与registerd_buffers数组联系起来,这样就可以找到哪个buffer页中记录了哪些日志数据。

1. XLogRegisterData

       将WAL数据日志注册到rdatas数组。找到rdatas数组中第一个空的位置,将传入的参数赋值给这个空位置。每注册一个就会占用一个槽位。

/*
 * Add data to the WAL record that's being constructed.将数据加入构建好的WAL记录
 *
 * The data is appended to the "main chunk", available at replay with XLogRecGetData().
这些数据会被加到main chunk部分,在XLogRecGetData函数执行replay操作时会用到
 */
void
XLogRegisterData(char *data, int len)
{
	XLogRecData *rdata;

	Assert(begininsert_called);

	if (num_rdatas >= max_rdatas)
		elog(ERROR, "too much WAL data");
	rdata = &rdatas[num_rdatas++];   //rdata是rdatas数组中的一个元素,前面我们也提到过,rdatas数组中的元素就是XLogRecData结构体。
	rdata->data = data;
	rdata->len = len;

	/*
	 * we use the mainrdata_last pointer to track the end of the chain, so no need to clear 'next' here. mainrdata_last->next始终指向链表末端
	 */

	mainrdata_last->next = rdata;
	mainrdata_last = rdata;

	mainrdata_len += len;
}

2. XLogRegisterBuffer

       将被修改的buffer页面信息注册到registerd_buffers数组

       在pg 9.5之前,与数据页面相关的信息同样会被注册到rdatas数组,这种实现逻辑不清晰。因此9.5之后改进了日志记录的产生和组织方式,通过XLogRegisterBuffer函数把被修改的页面注册到registerd_buffers数组中,每个页面在registerd_buffers数组中都占一个槽位。

/*
 * Register a reference to a buffer with the WAL record being constructed.
 * This must be called for every page that the WAL-logged operation modifies.
* 将构建好的WAL记录注册到buffer中,凡是需要记录日志的操作,其中的每个页都会调用该函数
 */
void
XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
{
	registered_buffer *regbuf;  //registerd_buffer结构体,是registerd_buffers数组中的一个元素

	/* NO_IMAGE doesn't make sense with FORCE_IMAGE,如果设置了FORCE_IMAGE,再设NO_IMAGE不会生效 */
	Assert(!((flags & REGBUF_FORCE_IMAGE) && (flags & (REGBUF_NO_IMAGE))));
	Assert(begininsert_called);

	if (block_id >= max_registered_block_id)
	{
		if (block_id >= max_registered_buffers)
			elog(ERROR, "too many registered buffers");
		max_registered_block_id = block_id + 1;
	}

	regbuf = &registered_buffers[block_id];  // 取出registerd_buffers数组中的一个元素,分配给刚创建的变量regbuf,后续由其赋值

	BufferGetTag(buffer, &regbuf->rnode, &regbuf->forkno, &regbuf->block);
	regbuf->page = BufferGetPage(buffer);
	regbuf->flags = flags;
	regbuf->rdata_tail = (XLogRecData *) &regbuf->rdata_head;
	regbuf->rdata_len = 0;

	/*
	 * Check that this page hasn't already been registered with some other
	 * block_id.检查这个page有没被其他块号注册过
	 */
#ifdef USE_ASSERT_CHECKING
	{
		int			i;

		for (i = 0; i < max_registered_block_id; i++)
		{
			registered_buffer *regbuf_old = &registered_buffers[i];

			if (i == block_id || !regbuf_old->in_use)
				continue;

			Assert(!RelFileNodeEquals(regbuf_old->rnode, regbuf->rnode) ||
				   regbuf_old->forkno != regbuf->forkno ||
				   regbuf_old->block != regbuf->block);
		}
	}
#endif

// 打个标记,本页已经在用了
	regbuf->in_use = true;
}

3. XLogRegisterBufData

       起到一个链接的作用,将registered_buffers数组与rdatas数组联系起来。

/*
 * Add buffer-specific data to the WAL record that's being constructed.
 */
void
XLogRegisterBufData(uint8 block_id, char *data, int len)
{
	registered_buffer *regbuf;
	XLogRecData *rdata;

	Assert(begininsert_called);

	/* find the registered buffer struct,第一个参数block_id就是XLogRegisterBuffer函数注册页面后占用的槽位 */
	regbuf = &registered_buffers[block_id];
	if (!regbuf->in_use)
		elog(ERROR, "no block with id %d registered with WAL insertion",
			 block_id);

	if (num_rdatas >= max_rdatas)
		elog(ERROR, "too much WAL data");
	rdata = &rdatas[num_rdatas++];

/* 将rdatas数组与registerd_buffers数组联系起来 */
	rdata->data = data;
	rdata->len = len;

	regbuf->rdata_tail->next = rdata;
	regbuf->rdata_tail = rdata;
	regbuf->rdata_len += len;
}

 五、 insert语句调用注册函数

        当我们执行一个最简单的insert语句,会看到在heap_insert函数中调用注册函数如下:

//xlrec为xl_heap_insert结构体
XLogRegisterData((char *) &xlrec, SizeOfHeapInsert);		
XLogRegisterBuffer(0, buffer, REGBUF_STANDARD | bufflags);
//xlhdr为xl_heap_header结构体
XLogRegisterBufData(0, (char *) &xlhdr, SizeOfHeapHeader);
//(char *) heaptup->t_data + SizeofHeapTupleHeader为实际元组
XLogRegisterBufData(0,
                    (char *) heaptup->t_data + SizeofHeapTupleHeader,
					heaptup->t_len - SizeofHeapTupleHeader);

       可以看到,heap_insert注册了xl_heap_insert(main data)、xl_heap_header(block data)、实际元组数据。回顾下前面阐述过的XLOG的组成部分:

 

注册之后XLOG填充进度为(红色暂无数据、绿色已有数据):

XLogRecord+XLogRecordBlockHeader+RelFileNode+BlockNumber + mainrdata_len +

xl_heap_header + 实际元组数据 + xl_heap_insert

下一步,日志组装函数XLogRecordAssemble就负责填充红色部分,并将以上所有数据组装成XLogRecData链表。

参考

PostgreSQL技术内幕:事务处理深度探索》第4章

postgres预写式日志的内核实现详解-wal记录写入-低调大师优秀的个人博客

https://cloud.tencent.com/developer/article/2000646

PostgreSQL重启恢复---XLOG 1.0_obvious__的博客-CSDN博客

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值