前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(65)新快照体系Globalvis工作原理分析

Postgresql源码(65)新快照体系Globalvis工作原理分析

作者头像
mingjie
发布2022-08-03 18:27:24
6420
发布2022-08-03 18:27:24
举报

相关: 《Postgresql源码(18)PGPROC相关结构》 《Postgresql源码(65)新快照体系Globalvis工作原理分析》 《Postgresql快照优化Globalvis新体系分析(性能大幅增强)》 《Improving Postgres Connection Scalability: Snapshots》 一些历史悠久的分析: 《Postgresql源码(28)获取快照GetSnapshotData流程分析和性能问题》

1 优化后的allProcs数组

先介绍下新版本PG删除了PGXACT结构后,事务ID是怎么存的:

  • 之前的PGPROC和PGXACT结构是一一对应的,每个后端一个PGPROC、一个PGXACT,事务ID记录在PGXACT中,问题就像上一篇提的,PGXACT不是紧凑的,遍历起来无法高效利用cache。
  • 新版本中,在PROC_HDR中原来的allPgXact不见了(指向PGXACT大数组),取而代之的是xids紧凑数组,注意!这个数组和pgprocnos索引是对应的。
  • 所以找一个PROC的xid,除了直接从PGPROC中读,还可以从PROC_HDR->xids读取,这个数组是紧凑的,遍历起来效率更高。

debug命令备忘(假设numProcs=3) p *procArray p allProcs[procArray->pgprocnos0] p allProcs[procArray->pgprocnos1] p allProcs[procArray->pgprocnos2] p ProcGlobal->xids0 p ProcGlobal->xids1 p ProcGlobal->xids2

2 GetSnapshotData第一部分——循环拿活跃事务

总结:

  • 遍历ProcGlobal->xids数组,拿到所有的xid(优化一:紧凑数组)。
  • 活跃的xid添加到快照的xip数组中。
  • 顺便算一个xmin(函数栈变量)记录最小的活跃事务id。
  • 读取PGXACT->xmin(优化二)
  • xmax在进入循环前就算好了,一定是latestCompletedXid+1。
  • 最后如果MyProc的xmin是无效的,更新一次PGPROC->xmin(还是要更新自己的xmin,这是必须的,无优化)
  • 整个过程包在ProcArrayLock大锁中。

代码流程:

代码语言:javascript
复制
GetSnapshotData
  ...
  LWLockAcquire(ProcArrayLock, LW_SHARED)
  ...
  for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
    TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
    
    /* 如果xid>=xmax可以直接跳过,因为这个xid是在我拿快照后启动的,对我来说肯定不可见 */
    if (!NormalTransactionIdPrecedes(xid, xmax))
      continue;    
    
    /* 如果xid < xmin,xid也要包含在xmin中,xmin表示最小的活跃事务 */
    if (NormalTransactionIdPrecedes(xid, xmin))
      xmin = xid;
    
    /* 记录到快照中 */
    xip[count++] = xid;
  
  /* 更新一次xmin */
  if (!TransactionIdIsValid(MyProc->xmin))
    MyProc->xmin = TransactionXmin = xmin;

  LWLockRelease(ProcArrayLock);

3 GetSnapshotData第二部分——维护GlobalVis

这部分要对比历史代码来看。

PG10

  • 【0】前面把快照已经计算完了。下面“顺便”计算下全局最小可清理位点:RecentGlobalXmin。
  • 【1】拿到最小事务ID。
  • 【2】最小可清理位点传给RecentGlobalXmin,如果配了vacuum_defer_cleanup_age,把最小可清理位点,再往前推,清理时会保留更多的事务,给高延迟的备机使用。
  • 【3】如果复制槽指定了某个位点,vacuum是不能清理的。
代码语言:javascript
复制
GetSnapshotData
    //【0】前面把快照已经计算完了。下面“顺便”计算下全局最小可清理位点:RecentGlobalXmin
    
    //【1】拿到最小事务ID
	if (TransactionIdPrecedes(xmin, globalxmin))
		globalxmin = xmin;
    
	//【2】最小可清理位点传给RecentGlobalXmin,如果配了vacuum_defer_cleanup_age
	// 把最小可清理位点,再往前推,清理时会保留更多的事务,给高延迟的备机使用
	RecentGlobalXmin = globalxmin - vacuum_defer_cleanup_age;
	if (!TransactionIdIsNormal(RecentGlobalXmin))
		RecentGlobalXmin = FirstNormalTransactionId;

	//【3】如果复制槽指定了某个位点,vacuum是不能清理的;
	if (TransactionIdIsValid(replication_slot_xmin) &&
		NormalTransactionIdPrecedes(replication_slot_xmin, RecentGlobalXmin))
		RecentGlobalXmin = replication_slot_xmin;
    ...
    ...

PG14

测试场景:

代码语言:javascript
复制
事务一RC-----------4000440启动-------------------4000440结束------------------------------->
事务二RR----------------------4000450启动-----------------------------4000450结束---------->
事务三RC--------------------------------------------调试位置------------------------------->

调试位置:

代码语言:javascript
复制
GetSnapshotData
    //【0】前面把快照已经计算完了。下面“顺便”计算下全局最小可清理位点,但是由于没有遍历PGPROC的xmin,只有自己遍历出来的xid,

	/* maintain state for GlobalVis* */
	{
		TransactionId def_vis_xid;
		TransactionId def_vis_xid_data;
		FullTransactionId def_vis_fxid;
		FullTransactionId def_vis_fxid_data;
		FullTransactionId oldestfxid;

        //【1】oldestfxid = 726
		oldestfxid = FullXidRelativeTo(latest_completed, oldestxid);
        
        //【2】def_vis_xid_data = 4000450
		def_vis_xid_data =
			TransactionIdRetreatedBy(xmin, vacuum_defer_cleanup_age);
        //【3】def_vis_xid_data = 4000450
		def_vis_xid_data =
			TransactionIdOlder(def_vis_xid_data, replication_slot_xmin);
        
        //【4】def_vis_xid = 4000450
		def_vis_xid = def_vis_xid_data;
		def_vis_xid = TransactionIdOlder(replication_slot_catalog_xmin, def_vis_xid);
		def_vis_fxid = FullXidRelativeTo(latest_completed, def_vis_xid);
		def_vis_fxid_data = FullXidRelativeTo(latest_completed, def_vis_xid_data);

        //【5】4000440 ----> 4000450
		GlobalVisSharedRels.definitely_needed =
			FullTransactionIdNewer(def_vis_fxid,
								   GlobalVisSharedRels.definitely_needed);
        ...
        //【6】4000440 ----> 4000440
		GlobalVisSharedRels.maybe_needed =
			FullTransactionIdNewer(GlobalVisSharedRels.maybe_needed,
								   oldestfxid);
        ...
	}

总结:

  • 【5】4000440 ----> 4000450:definitely_needed计算时实际上用的就是上面循环里面扫出来的最小的xid,在经过参数微调下(vacuumdelay和复制槽),definitely_needed的含义比较好理解,运行中的事务都是>=这个值的。
  • 【6】4000440 ----> 4000440:maybe_needed取值是取:newer(自己的值、最小的冻结事务ID),这个值的含义是:小于这个值的事务应该都不活跃了。
  • 不活跃 < maybe_needed < ... < definitely_needed <= 活跃,那maybe_needed和definitely_needed中间的值怎么知道有没有提交?

看下面函数:

代码语言:javascript
复制
bool
GlobalVisTestIsRemovableFullXid(GlobalVisState *state,
								FullTransactionId fxid)
{
    // 比maybe_needed小的肯定已经不活跃了,可以清理!
	if (FullTransactionIdPrecedes(fxid, state->maybe_needed))
		return true;

    // 比definitely_needed大于或等于,肯定活跃的,不能清理!
	if (FullTransactionIdFollowsOrEquals(fxid, state->definitely_needed))
		return false;

    // 中间的怎么办?调用GlobalVisUpdate
	if (GlobalVisTestShouldUpdate(state))
	{
	    // 调用ComputeXidHorizons计算每一个PROC的xmin,然后更新到GlobalVis变量中。
		GlobalVisUpdate();

		Assert(FullTransactionIdPrecedes(fxid, state->definitely_needed));

		return FullTransactionIdPrecedes(fxid, state->maybe_needed);
	}
	else
		return false;
}
  • (粗略判断)在判断具体一个XID能不能清理时,如果比definitely_needed大(或等),或比maybe_needed小,可以直接返回。
  • (需要时精确判断)在判断maybe_needed和definitely_needed中间的xid时,要走优化前的老逻辑,遍历每个PGPROC的xmin,拿到全局最小的xmin,然后调用GlobalVisUpdateApply更新全局变量GlobalVisSharedRels.maybe_needed、GlobalVisSharedRels.definitely_needed的值,提供给后面函数使用。

4 (重要)优化后不在循环内读取PGXACT->xmin会有什么影响?

  1. 循环PGPROC时,看不到别的进程的xmin了,例如,一个RR事务A在拿快照的时候,最小事务ID:10还没提交,那么这个RR快照的xmin就是10。
  2. 优化前,A事务后面新事务B构造快照时,能通过A的PGPROC看到xmin=10,并更新自己的xmin=10。
  3. 优化后,A事务后面新事务B构造快照时,只能看到自己遍历时最小的xid,例如这时10已经提交了,B看到的是15,那么后构造的快照就会认为全局最小xmin=15,但是由于A的xmin是10,所以这时全局清理位点需要是10,不能是15。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 优化后的allProcs数组
  • 2 GetSnapshotData第一部分——循环拿活跃事务
  • 3 GetSnapshotData第二部分——维护GlobalVis
    • PG10
      • PG14
      • 4 (重要)优化后不在循环内读取PGXACT->xmin会有什么影响?
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档