pg内核之日志管理器(二)MultiXact日志

概念

MultiXact日志时数据库用来记录组合事务ID的日志。正常情况下,当事务更新一个元组或者对一个元组加锁时,其xmax字段会填入操作该元组的事务ID值,但是当多个事务同时操作一个元组时(如更新元组事务和元组加锁事务),元组的xmax就无法同时记录两个及以上的事务ID了,这时就会分配一个MultiXactID,然后将事务ID保存到MultiXact日志中,这样需要时就能通过MultiXactID就能查到其对应的事务ID了。
所以MultiXactID跟事务ID是一对多的关系,需要再事务ID数组中标记哪一段映射到MultiXactID中,所以映射过程总需要存储两种信息: offset和nmembers
MultiXact日志管理器是基于SLRU缓冲池实现的,只不过它需要两个缓冲池,分别是MultiXactOffsetCtlData和MultiXactMemberCtlData
在日志文件中存储的offset和members日志文件:
offset日志: 每个日志文件相当于一个段,每个段有32个页,由于每个offset是32位的也就是4个字节,所以每页可以存8K/4=2K个,每个文件可以存32*2K=64K个
members日志: 每个日志文件相当于一个段,每个段32个页,由于每个member会有一个flag标识状态,每个flag占1个字节,所以每4个member加上一个字节的flag组成一个group大小事20字节,每个页就占了409个group,每个段实际保存的事务ID数量为32 * 409 * 4 = 52352个。

在这里插入图片描述

offset可以通过三元组定位<segno, pageno, offno>,其中

  • segno: 段号
  • 段内页码
  • 页内偏移
    给定一个MultiXactID可以通过三元组计算出offset的位置,其公式为:
segno = mxid / 65536
pageno = mxid /2048
offno = mxid % 2048 * 4

member也可以通过三元组来定位其位置,给定一个offset,其计算公式为

segno = offset /52352
pageno = offset / 1636
groupno = (((offset/4) % 409)
offno = (((offset/4) % 409) * 20 + 4 + (offset % 4)*4
flagByte = (((offset/4) % 409) * 20
flag = offset % 4 * 8

例如,mxid为2050, offset位置为<0,1,8>, 假如得出其中一个的offset值为1836,其member位置<0,1,1004>
Multixact一般包含多个member,其他的members的位置一般紧邻着第一个members存放,所以只要知道第一个members的位置和member的数量即可,数量可以通过mxid+1的offset值减去mxid的offset值获得。

MultiXact日志管理器的结构

MultiXactStateData

维护全局的MultiXactID信息

typedef struct MultiXactStateData
{
	MultiXactId nextMXact; //下一个可分配的MultiXact ID
	MultiXactOffset nextOffset; //下一个可分配的MultiXact offset
	bool		finishedStartup; //是否MultiXact已经启动完成
	MultiXactId oldestMultiXactId; //最老的MultiXact ID
	Oid			oldestMultiXactDB; //最老的MultiXact的database ID
	MultiXactOffset oldestOffset; //最老的offset值
	bool		oldestOffsetKnown;
	MultiXactId multiVacLimit; //可能需要进行清理的MultiXact值阈值
	MultiXactId multiWarnLimit; //MultiXACT的警告阈值
	MultiXactId multiStopLimit; //MultiXact的停止阈值
	MultiXactId multiWrapLimit; //MultiXact的回卷阈值
	MultiXactOffset offsetStopLimit;/* known if oldestOffsetKnown */
	MultiXactId perBackendXactIds[FLEXIBLE_ARRAY_MEMBER]; //保存的MultiXactID数组
} MultiXactStateData;

mXactCacheEnt

 /*
 将MultiXact ID信息的操作结构体,可以使用该缓存查询MultiXact信息,而不用每次都去SLRU缓冲池中查询
 包含MultiXact即对应的member信息,通过链表连接
 */
typedef struct mXactCacheEnt
{
	MultiXactId multi; //MultiXact ID
	int			nmembers; //member数量
	dlist_node	node; //链表
	MultiXactMember members[FLEXIBLE_ARRAY_MEMBER]; //member信息
} mXactCacheEnt;

MultiXact日志的主要操作函数

MultiXact的初始化

MultiXactShmemInit

初始化MultiXact缓冲池及MultiXactStateData结构

  • 初始化MultiOffset缓冲池,初始化大小为8个SLRU缓冲池槽(约64K~1M),设置MultiOffset比较回调函数
  • 初始化MultiMembers缓冲池,初始化大小为16个缓冲池槽(约128K~1M),设置MultiMembers比较回调函数
  • 初始化MultiXactStateData全局变量,初始化大小为8 * ( max_prepared_xacts + max_backends) + 4
	MultiXactOffsetCtl->PagePrecedes = MultiXactOffsetPagePrecedes;
	MultiXactMemberCtl->PagePrecedes = MultiXactMemberPagePrecedes;

	SimpleLruInit(MultiXactOffsetCtl,
				  "MultiXactOffset", NUM_MULTIXACTOFFSET_BUFFERS, 0,
				  MultiXactOffsetSLRULock, "pg_multixact/offsets",
				  LWTRANCHE_MULTIXACTOFFSET_BUFFER,
				  SYNC_HANDLER_MULTIXACT_OFFSET); //初始化offset
	SlruPagePrecedesUnitTests(MultiXactOffsetCtl, MULTIXACT_OFFSETS_PER_PAGE);
	SimpleLruInit(MultiXactMemberCtl,
				  "MultiXactMember", NUM_MULTIXACTMEMBER_BUFFERS, 0,
				  MultiXactMemberSLRULock, "pg_multixact/members",
				  LWTRANCHE_MULTIXACTMEMBER_BUFFER,
				  SYNC_HANDLER_MULTIXACT_MEMBER); //初始化member
	MultiXactState = ShmemInitStruct("Shared MultiXact State",
									 SHARED_MULTIXACT_STATE_SIZE,
									 &found); //初始化全局状态结构体

MultiXact的创建

MultiXactIdCreate

创建一个MultiXactID用来代表两个事务ID

  • 创建一个members[2]数组并将要关联的两个事务ID信息填充到members数组
	MultiXactMember members[2]; //members的数组,创建的时候只能是2个事务ID,其他的后面可以通过扩容扩进来
	members[0].xid = xid1;
	members[0].status = status1;
	members[1].xid = xid2;
	members[1].status = status2;
  • 调用MultiXactIdCreateFromMembers函数创建一个MultiXactID并返回
newMulti = MultiXactIdCreateFromMembers(2, members); //创建一个MultiXact ID

MultiXactIdCreateFromMembers

根据members创建一个MultiXact ID

  • 判断members是否已经分配过MultiXact ID,如果已经分配过直接返回缓存中的MultiXact ID
	multi = mXactCacheGetBySet(nmembers, members); //判断这些members是否已经在cache中已经存在,若已经存在返回其值
	if (MultiXactIdIsValid(multi))
	{
		debug_elog2(DEBUG2, "Create: in cache!");
		return multi;
	}
  • 判断member中是否有UPDATE操作,如果有,则设置has_update字段为true,主要是通过判断member的status来确定
	/* 判断member中是否有UPDATE操作 */
	{
		int			i;
		bool		has_update = false;

		for (i = 0; i < nmembers; i++)
		{
			if (ISUPDATE_from_mxstatus(members[i].status))//根据status判断
			{
				if (has_update)
					elog(ERROR, "new multixact has more than one updating member");
				has_update = true;
			}
		}
	}
  • 调用GetNewMultiXactId函数获取一个新事务ID
multi = GetNewMultiXactId(nmembers, &offset); //获取一个新的MultiXact ID
  • 写入XLOG中
	xlrec.mid = multi;
	xlrec.moff = offset;
	xlrec.nmembers = nmembers;
	XLogBeginInsert();
	XLogRegisterData((char *) (&xlrec), SizeOfMultiXactCreate);
	XLogRegisterData((char *) members, nmembers * sizeof(MultiXactMember));

	(void) XLogInsert(RM_MULTIXACT_ID, XLOG_MULTIXACT_CREATE_ID);
  • 调用RecordNewMultiXact函数将offset和members写入MultiXact日志中
RecordNewMultiXact(multi, offset, nmembers, members); //将offset和members写入文件
  • 调用mXactCachePut函数将新的MultiXact ID写入缓存中
mXactCachePut(multi, nmembers, members);

GetNewMultiXactId

获取的新的MultiXact ID值

  • 从全局变量MultiXactState上取nextMultiId为新的MultiXACTID。
  • 判断新的MultiXact是否达到回卷限制值
  • 从全局变量MultiXactState取nextOffset作为新的offset值
  • 判断members缓冲池中有足够的空间保存新的members
  • 更新全局变量MultiXactState的nextMultiId和nextOffset值(加1)

RecordNewMultiXact

将新的MultiXactID及对应的offset和members写入到MultiXact日志文件中,实际上只是写入到对应的SLRU缓冲区的页槽中,后续由缓冲区统一刷入磁盘。

  • 获取要写的缓冲页的页号,页内偏移等地址信息
  • 将新的MultiXact信息写入到对应的缓冲区

MultiXact的扩展

MultiXactIdExpand

将一个事务ID扩展到已有的MultiXact ID中,MultiXact ID创建时只能选择两个事务ID,如果是有2个以上的事务ID时,就会先基于两个创建MultiXact ID后再将其他的事务ID扩展进去,扩展后会生成新的MultiXact ID。但是由于扩展过程中还要考虑已有的members的运行情况,所以扩展后结果可能会变成1个也有可能

  • 根据MultiXact ID读出原有的members信息
  • 如果读出的members为0,那么就基于现在传入的事务ID简单的创建一个新的MultiXact
	nmembers = GetMultiXactIdMembers(multi, &members, false, false);//读出MultiXact ID的members

	if (nmembers < 0)
	{
		MultiXactMember member;

		/*
		 如果已有的MultiXact ID的members已经停止运行,那么就只会简单的返回一个事务ID的新MultiXact ID
		 这种情况很不常见。
		 */
		member.xid = xid;
		member.status = status;
		newMulti = MultiXactIdCreateFromMembers(1, &member);
		return newMulti;
	}
  • 如果要扩展的事务ID已经是MultiXact ID原有的member中的一个,且状态也一样,那么直接返回该MultiXact ID即可。
	for (i = 0; i < nmembers; i++)
	{
		if (TransactionIdEquals(members[i].xid, xid) &&
			(members[i].status == status))
		{
			debug_elog4(DEBUG2, "Expand: %u is already a member of %u",
						xid, multi);
			pfree(members);
			return multi;
		}
	}
  • 将原有的事务ID和传入的待扩展的一起新建一个MultiXact ID,这里如果原有的members已经不再运行或者更新的事务还未提交,那么这些member将不会包含其中看,所以这里也有可能最后创建出的MultiXact ID只有一个member
	newMembers = (MultiXactMember *)
		palloc(sizeof(MultiXactMember) * (nmembers + 1));

	for (i = 0, j = 0; i < nmembers; i++)
	{
		if (TransactionIdIsInProgress(members[i].xid) ||
			(ISUPDATE_from_mxstatus(members[i].status) &&
			 TransactionIdDidCommit(members[i].xid)))
		{
			newMembers[j].xid = members[i].xid;
			newMembers[j++].status = members[i].status;
		}
	}

MultiXact的查询

GetMultiXactIdMembers

通过元组中的MultiXact ID先取offset日志中读取对应的offset值,然后读取MultiXact ID +1 的offset值,两者相减即得出members的length,然后通过offset获取members的存储的起始位置,然后读取lenghth个member即是该MultiXact的所有members。

  • 先在缓存中判断是否能够查询到对应的members信息,如果可以直接返回即可
  • 设置最旧的可见的MultiXact ID
  • 判断MultiXact的状态
    • 如果MultiXact小于数据库中最老的oldestMXact,则说明该MultiXact ID已经不存在
    • 如果MultiXact大于下一个要分配的MultiXact ID,说明该ID还未被创建
  • 下面就是从MultiXact日志找出members的流程,需要确定的是在members日志中的起始位置及偏移量,先从offset日志读出该MultiXact ID对应的offset值,如果该MultiXact ID是最新的,那么直接那nextOffset减去offset值就是members的偏移
  • 读取MultiXact ID + 1对应的offset值,减去上面MultiXact ID的offset值就是MultiXact ID的members的长度。
  • 分配空间并保存对应的members值,有了offset值和nmembers就可以一个个读出member值。
  • 将读出的值保存到缓存中一份。

MultiXact的启动

StartupMultiXact

启动时从共享内存中读取出nextMaxct和nextOffset,然后更新到SLRU的缓冲池的latest_page_number中。

MultiXACT的删除

TruncateMultiXact

清除比较久的offset的段文件和member的段文件,这个函数直返会vacuum的时候调用。

  • 如果传入的oldestMultiID小于全局变量中保存的MultiXactID,说明不需要删除,直接返回
  • 如果传入的oldestMultiId小于段文件中最老的MultiXact ID,说明不需要删除直接返回
  • 计算出member中的可以安全删除的位置
  • 计算出要删除的member中的位置
  • 删除动作写XLOG日志
  • 删除members文件
  • 删除offset文件

MultiXact的检查点操作

CheckPointMultiXact

将offset和member的SLRU缓冲区中的所有的数据写入磁盘

MultiXact的REDO

multixact_redo

由于MultiXact日志在在创建或者删除等操作时都写了XLOG日志,所以在故障恢复时可以通过REDO操作恢复对应的操作。MultiXact日志的REDO主要有四种类型

  • XLOG_MULTIXACT_ZERO_OFF_PAGE :初始化offset缓冲池的page
  • XLOG_MULTIXACT_ZERO_MEM_PAGE: 初始化members缓冲池的page
  • XLOG_MULTIXACT_CREATE_ID:创建MultiXact ID的操作
  • XLOG_MULTIXACT_TRUNCATE_ID: 删除旧的段文件的操作

【参考】

  1. 《PostgreSQL数据库内核分析》
  2. 《Postgresql技术内幕-事务处理深度探索》
  3. pg14源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值