PolarDB数据库的CSN机制

news2024/12/23 9:31:34

背景

对postgres数据库熟悉的同学会发现在高并发场景下在获取快照处易出现性能瓶颈,其原因在于PG使用全局数组在共享内存中保存所有事务的状态,在获取快照时需要加锁以保证数据一致性。获取快照时需要持有ProcArraryLock共享锁比遍历ProcArray数组中活跃事务,与此同时提交或回滚的事务需要申请ProcArray排他锁已清除本事务。可想而知,在高并发场景下对ProcArrayLock的申请会成为数据库的瓶颈。为克服上述问题,polardb引入CSN(COMMIT SEQUENCE NUM)事务快照机制避免对ProcarryLock的申请。

1 CSN 机制

1.1 CSN原理

PolarDB在事务层,通过CSN快照来代替PG原生快照
在这里插入图片描述
如图所示,每个非只读事务在运行过程中会被分配一个xid,在事务提交时推进CSN;同时会将单前的CSN与事务的XID的映射关系保存起来。
图中实心竖线标识获取快照时刻,会获取最新提交CSN的下一个值4。TX1、TX3、TX5均已提交,其对应的CSN为1、2、3。TX2、TX4、TX6正在运行,TX6、TX8是未来还未开启的事务。对于当前快照而言,严格小于CSN=4的事务的提交结果均可见;其余事务还未提交,不可见。

1.2 CSN的实现

CSN(Commit Sequence Number,提交顺序号)本身与XID(事务号)也会留存一个映射关系,以便将事务本身以及其对应的可见性进行关联,这个映射关系会留存在CSNLog中。事务ID 2048、2049、2050、2051、2052、2053对应的CSN号依次是5、4、7、10、6、8,也就是事务的提交顺序是2049、2048、2052、2050、2053、2051.
在这里插入图片描述
PolarDB与之对应为每个事务id分配8个字节uint64的CSN号,所以一个8kB页面能保存1k个事务的CSN号。CSNLOG达到一定大小后会分块,每个CSNLOG文件块的大小为256kB。同xid号类似,CSN号预留了几个特殊的号。CSNLOG定义代码如下:
在这里插入图片描述

2 CSN快照与可见性判断

2.1 CSN相关数据结构

polar_csn_mvcc_var_cache结构体维护了最老的活跃事务xid、下一个将要分配的CSN以及最新完成的事务xid。
在这里插入图片描述
当其他事务要获取该事务的CSN状态时,如果该事务处于正在提交阶段,那么其他事务通过获取CommitSeqNoLock锁的排他模式来等待其完成。
CSNLogControlLock用于写入csnlog文件时加锁保护。

2.2 CSN快照的获取

PolarDB中获取CSN快照函数为GetSnapshotDataCSN,实现流程如下:
1、获取polar_shmem_csn_mvcc_var_cache->polar_next_csn作为snapshot->polar_snapshot_csn值。
2、snapshot->xmin = polar_shmem_csn_mvcc_var_cache->polar_oldest_active_xid
3、snapshot->xmax=polar_shmem_csn_mvcc_var_cache->polar_latest_completed_xid+1
4、根据GUC参数old_snapshot_threshold,决定是否需要设置snapshot->lsn以及snapshot->whenTaken 。
5、最后根据GUC参数polar_csn_xid_snapshot表示是否从csn快照中生成xid快照。

tatic Snapshot
GetSnapshotDataCSN(Snapshot snapshot)
{
	TransactionId xmin;
	TransactionId xmax;
	CommitSeqNo snapshotcsn;

	Assert(snapshot != NULL);

	/*
	 * The ProcArrayLock is not needed here. We only set our xmin if
	 * it's not already set. There are only a few functions that check
	 * the xmin under exclusive ProcArrayLock:
	 * 1) ProcArrayInstallRestored/ImportedXmin -- can only care about
	 * our xmin long after it has been first set.
	 * 2) ProcArrayEndTransaction is not called concurrently with
	 * GetSnapshotData.
	 */

	/* Anything older than oldestActiveXid is surely finished by now. */
	xmin = pg_atomic_read_u32(&polar_shmem_csn_mvcc_var_cache->polar_oldest_active_xid);
	/* If no performance issue, we try best to maintain RecentXmin for xid based snapshot */
	RecentXmin = xmin;

	/* Announce my xmin, to hold back GlobalXmin. */
	if (!TransactionIdIsValid(MyPgXact->xmin))
	{
		TransactionId oldest_active_xid;

		MyPgXact->xmin = xmin;
		TransactionXmin = xmin;

		/*
		 * Recheck, if oldestActiveXid advanced after we read it.
		 *
		 * This protects against a race condition with GetRecentGlobalXmin().
		 * If a transaction ends runs GetRecentGlobalXmin(), just after we fetch
		 * polar_oldest_active_xid, but before we set MyPgXact->xmin, it's possible
		 * that GetRecentGlobalXmin() computed a new GlobalXmin that doesn't
		 * cover the xmin that we got. To fix that, check polar_oldest_active_xid
		 * again, after setting xmin. Redoing it once is enough, we don't need
		 * to loop, because the (stale) xmin that we set prevents the same
		 * race condition from advancing RecentGlobalXmin again.
		 *
		 * For a brief moment, we can have the situation that our xmin is
		 * lower than RecentGlobalXmin, but it's OK because we don't use that xmin
		 * until we've re-checked and corrected it if necessary.
		 */

		/*
		 * memory barrier to make sure that setting the xmin in our PGPROC entry
		 * is made visible to others, before the read below.
		 */
		pg_memory_barrier();

		oldest_active_xid  = pg_atomic_read_u32(&polar_shmem_csn_mvcc_var_cache->polar_oldest_active_xid);
		if (oldest_active_xid != xmin)
		{
			/*no cover begin*/
			xmin = oldest_active_xid;

			RecentXmin = xmin;
			MyPgXact->xmin = xmin;
			TransactionXmin = xmin;
			/*no cover end*/
		}
	}

	/*
	 * Get the current snapshot CSN. This
	 * serializes us with any concurrent commits.
	 */
	snapshotcsn = pg_atomic_read_u64(&polar_shmem_csn_mvcc_var_cache->polar_next_csn);
	
	/*
	 * Also get xmax. It is always latestCompletedXid + 1.
	 * Make sure to read it after CSN (see TransactionIdAsyncCommitTree())
	 */
	pg_read_barrier();
	xmax = pg_atomic_read_u32(&polar_shmem_csn_mvcc_var_cache->polar_latest_completed_xid);
	Assert(TransactionIdIsNormal(xmax));
	TransactionIdAdvance(xmax);

	snapshot->xmin = xmin;
	snapshot->xmax = xmax;
	snapshot->polar_snapshot_csn = snapshotcsn;
	snapshot->polar_csn_xid_snapshot = false;
	snapshot->xcnt = 0;
	snapshot->subxcnt = 0;
	snapshot->suboverflowed = false;
	snapshot->curcid = GetCurrentCommandId(false);

	/*
	 * This is a new snapshot, so set both refcounts are zero, and mark it as
	 * not copied in persistent memory.
	 */
	snapshot->active_count = 0;
	snapshot->regd_count = 0;
	snapshot->copied = false;

	if (old_snapshot_threshold < 0)
	{
		/*
		 * If not using "snapshot too old" feature, fill related fields with
		 * dummy values that don't require any locking.
		 */
		snapshot->lsn = InvalidXLogRecPtr;
		snapshot->whenTaken = 0;
	}
	else
	{
		/*
		 * Capture the current time and WAL stream location in case this
		 * snapshot becomes old enough to need to fall back on the special
		 * "old snapshot" logic.
		 */
		snapshot->lsn = GetXLogInsertRecPtr();
		snapshot->whenTaken = GetSnapshotCurrentTimestamp();
		MaintainOldSnapshotTimeMapping(snapshot->whenTaken, xmin);
	}

	/* 
	 * We get RecentGlobalXmin/RecentGlobalDataXmin lazily in polar csn.
	 * In master mode, we reset it when end transaction;
	 * In hot standby mode, wal replayed by startup backend, we has to reset
	 * it when get snapshot,
	 * because RecentGlobalXmin/RecentGlobalDataXmin are backend variables.
	 */
	if (RecoveryInProgress())
		resetGlobalXminCacheCSN();

	/* 
	 * We need xid snapshot, should generate it from csn snapshot.
	 * The logic is:
	 * 1. Scan csnlog from xmin(inclusive) to xmax(exclusive)
	 * 2. Add xids whose status are in_progress or committing or 
	 *    committed csn >= snapshotcsn to xid array
	 * Like hot standby, we don't know which xids are top-level and which are
	 * subxacts. So we use subxip to store xids as more as possible. 
	 */
	if (polar_csn_xid_snapshot)
	{
		if (TransactionIdPrecedes(xmin, xmax))
			polar_csnlog_get_running_xids(xmin, xmax, snapshotcsn, GetMaxSnapshotSubxidCount(),
				&snapshot->subxcnt, snapshot->subxip, &snapshot->suboverflowed);

		snapshot->polar_csn_xid_snapshot = true;
	}

	return snapshot;
}
2.3 MVCC可见性判断流程

结合行头的结构(其中的xmin、xmax)以及Clog、上述CSNLOG的映射机制,MVCC的大致判断流程如下所示,实现函数为HeapTupleSatisfiesMVCC,对于xid在CSN快照中的可见性判断函数为XidVisibleInSnapshotCSN,其流程图如下:
在这里插入图片描述

2.4 事务commit和abort如何更新CSN

CSN快照获取主要依据polar_shmem_csn_mvcc_var_cache变量中维护的成员变量,参考前面的CSN快照获取。
因此,这里主要关注事务在commit和abort时如何更新polar_shmem_csn_mvcc_var_cache的成员变量。

AdvanceOldestActiveXidCSN函数用于推进->polar_oldest_active_xid这个值:
进程退出、事务提交以及回滚之后、以及在备机上回放commit和abort时需要推进polar_shmem_csn_mvcc_var_cache->polar_oldest_active_xid,当事务的xid等于polar_shmem_csn_mvcc_var_cache->polar_oldest_active_xid时,才会推进polar_shmem_csn_mvcc_var_cache->polar_oldest_active_xid的值,否则直接返回。

polar_xact_abort_tree_csn在事务回滚时设置CSN的值(POLAR_CSN_ABORTED),并推进polar_shmem_csn_mvcc_var_cache->polar_latest_completed_xid值。
polar_xact_commit_tree_csn在事务提交时设置该事务CSN的值,并推进和polar_shmem_csn_mvcc_var_cache->polar_latest_completed_xid和polar_shmem_csn_mvcc_var_cache->polar_next_csn的值。

polar_shmem_csn_mvcc_var_cache->polar_next_csn只有事务提交才会推进,回滚事务不会推进该值。

对于开启CSN功能之后,PG中原来的维护xid分配的全局变量ShmemVariableCache中的数据成员只有ShmemVariableCache->nextXid会更新(用于分配xid)。而原来的ShmemVariableCache->latestCompletedXid等在已经被polar_shmem_csn_mvcc_var_cache->polar_latest_completed_xid所取代,因此事务状态变化时并不需要维护其值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370955.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[计算机网络(第八版)]第二章 物理层(学习笔记)

网络层是网络体系结构的最低层&#xff0c;不是具体的传输媒体&#xff0c;也不是连接计算机的具体物理设备 2.1 物理层的概念 物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是指具体的传输媒体。物理层的作用&#xff1a; 要尽可能地屏…

面试题:Android 中 Intent 采用了什么设计模式?

答案是采用了原型模式。原型模式的好处在于方便地拷贝某个实例的属性进行使用、又不会对原实例造成影响&#xff0c;其逻辑在于对 Cloneable 接口的实现。 话不多说看下 Intent 的关键源码&#xff1a; // frameworks/base/core/java/android/content/Intent.java public cla…

阅读笔记9——DenseNet

一、DenseNet DenseNet的网络结构如图1-1所示&#xff0c;其核心是Dense Block模块&#xff0c;Dense Block中的一个黑点就代表一个卷积模块&#xff08;不是一个卷积层&#xff0c;而是DenseNet提出的一个BottleNeck模块&#xff0c;后文有讲解&#xff09;&#xff0c;每条黑…

ClassPathResource遇到的坑:class path resource

读取文件--ClassPathResource前言一、使用ClassPathResource.getFile()的坑二、通过流读取文件内容总结前言 需求&#xff1a;拿到一个小程序的皮肤文件夹&#xff0c;放在resource目录下 1:根据皮肤的style.json&#xff0c;获取json内的${xxx.png}变量&#xff08;获的图片名…

「2」指针进阶——详解

&#x1f680;&#x1f680;&#x1f680;大家觉不错的话&#xff0c;就恳求大家点点关注&#xff0c;点点小爱心&#xff0c;指点指点&#x1f680;&#x1f680;&#x1f680; 目录 &#x1f430;指向函数指针数组的指针(很少用&#xff0c;了解) &#x1f430;回调函数&…

【Arduino 无刷电机控制教程】

【Arduino 无刷电机控制教程】 1. 概述2. 试验准备3. 实验原理4. Arduino 无刷电机控制 – 电路图4.1 实验组件4.2 用于 BLDC 电机控制的 Arduino 代码5. 实验验证5.1 电位计控制无刷电机速度5.2 电调校准在本教程中,我们将学习如何使用 Arduino 和 ESC 控制无刷电机。如果您想…

建议将com.alibaba:fastjson升级至1.2.83

问题 升级了gradle&#xff0c;改了文件存储位置&#xff0c;项目需要重新构建下载依赖文件&#xff0c;发现fastjson 1.2.66一直下载不下来一直卡在下载&#xff0c;就想着手动下载下试试&#xff0c;就去了mvnrepository网站找到fastjson时&#xff0c;发现了fastjson2 Note…

一文让你彻底了解Linux内核文件系统

一&#xff0c;文件系统特点 文件系统要有严格的组织形式&#xff0c;使得文件能够以块为单位进行存储。文件系统中也要有索引区&#xff0c;用来方便查找一个文件分成的多个块都存放在了什么位置。如果文件系统中有的文件是热点文件&#xff0c;近期经常被读取和写入&#xf…

数学不好,英语不行,非本专业,可以学IT吗?

很多小伙伴&#xff0c;都会问小青一些比较类似的问题。比如&#xff1a;不是计算机专业的&#xff0c;可以学编程吗&#xff1f;数学一直就不好&#xff0c;可以转行学IT吗&#xff1f;学编程开发&#xff0c;对英语的的要求会不会很高&#xff1f;01计算机不是计算机专业的&a…

C/C++开发,无可避免的内存管理(篇三)-规划好内存

一、用内存空间换效率 1.1 allocatoe类模板 在前面简述模板顺序容器时&#xff0c;就提到过&#xff0c;标准库中的 vector 类是通过预先分配额外内存以换取不不用每次添加元素都要重新分配内存和移动元素&#xff0c;而是将元素直接保存加入的预先分配的内存区域。在预先分配…

【Git】Git冲突与解决方法

目录 一、Git冲突如何产生&#xff1f; 二、解决Git冲突—手动修改冲突 【第一步】在 hot-fix 分支上增加如下代码&#xff0c;并且提交。 【第二步】在master 分支上同样的地方增加如下代码&#xff0c;并且提交。 【第三步】 我们现在在 master 分支上合并 hot-fix 分支&a…

慢雾:Discord 私信钓鱼手法分析

事件背景 5 月 16 日凌晨&#xff0c;当我在寻找家人的时候&#xff0c;从项目官网的邀请链接加入了官方的 Discord 服务器。在我加入服务器后立刻就有一个"机器人"(Captcha.bot)发来私信要我进行人机验证。这一切看起来相当的合理。我也点击了这个验证链接进行查看…

数据结构——顺序表讲解

作者&#xff1a;几冬雪来 时间&#xff1a;2023年2月25日 内容&#xff1a;数据结构顺序表内容讲解 目录 前言&#xff1a; 顺序表&#xff1a; 1.线性表&#xff1a; 2.什么是顺序表&#xff1a; 3.顺序表的概念和构成&#xff1a; 4.顺序表的书写&#xff1a; 1…

【Web逆向】万方数据平台正文的逆向分析(上篇--加密发送请求)—— 逆向protobuf

【Web逆向】万方数据平台正文的逆向分析&#xff08;上篇--加密发送请求&#xff09;—— 逆向protobuf声明一、了解protobuf协议&#xff1a;二、前期准备&#xff1a;二、目标网站&#xff1a;三、开始分析&#xff1a;我们一句句分析&#xff1a;先for循环部分&#xff1a;后…

Servlet笔记(11):Servletcontext对象

1、什么是ServletContext ServletContext是一个全局储存空间&#xff0c;随服务器的生命周期变化&#xff0c; Cookie&#xff0c;Session&#xff0c;ServletContext的区别 Cookie&#xff1a; 存在于客户端的本地文本文件 Session&#xff1a; 存在于服务器的文本文件&#…

今天我在朋友圈看到的新京报公众号一段文章&#xff1a;十三届全国人大&#xff08;过几天就任期结束&#xff09;在第五次会议&#xff08;2022年3月5日&#xff09;对证监会提了一条第6178号建议《关于严厉打击风水盲测股市动向的建议》。今天&#xff0c;证监会进行了收称答…

业务代码编写过程中如何「优雅的」配置隔离

思考 不同的处理方式 1.常规的处理方式&#xff0c;通过某种规则判断区分代码环境 // 获取环境标识 const env getCurrentEnv();if (env dev) {// do something } else if (env test) {// do something } else if (env prod) {// do something } 分析&#xff1a; 1.此种…

Linux 操作系统——查看/修改系统时区、时间、本地时间修改为UTC

文章目录1.背景描述2.知识储备3.解决步骤1. 查看当前时区2.修改设置Linux服务器时区3.复制相应的时区文件&#xff0c;替换系统时区文件&#xff1b;或者创建链接文件4. 查看和修改Linux的时间5. 硬件时间和系统时间的 相互同步1.背景描述 最近一个项目日期采用java8的LocalDa…

你在公司混的差,可能和组织架构有关!

原创&#xff1a;小姐姐味道&#xff08;微信公众号ID&#xff1a;xjjdog&#xff09;&#xff0c;欢迎分享&#xff0c;非公众号转载保留此声明。如果你接触过公司的面试工作&#xff0c;一定见过很多来自大公司的渣渣。这些人的薪资和职位&#xff0c;比你高出很多&#xff0…

Delphi RSA加解密(二)

dll开发环境: Delphi XE 10.1 Berlin exe开发环境: Delphi 6 前提文章: Delphi RSA加解密(一) 目录 1. 概述 2. 准备工作 2.1 下载DEMO程序 2.2 字符编码说明 3. Cryption.dll封装 3.1 接口概况 3.2 uPub.pas单元代码 3.3 uInterface.pas单元代码 3.4 特别注意 4. 主程序…