前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(28)获取快照GetSnapshotData流程分析和性能问题

Postgresql源码(28)获取快照GetSnapshotData流程分析和性能问题

作者头像
mingjie
发布2022-07-14 13:44:07
2720
发布2022-07-14 13:44:07
举报

rc事务快照获取的一次实例和总结。

构造场景

代码语言:javascript
复制
--------活跃--------提交--------活跃--------回滚--------活跃--------提交
事务ID:1840251    1840252     1840253    1840254     1840255   1840256

五个事务ID执行内容:
1840251:begin;insert into a values (199);
1840252:begin;insert into a values (199);commit;
1840253:begin;insert into a values (199);
1840254:begin;insert into a values (200);rollback;
1840255:begin;insert into a values (199);
1840256:begin;insert into a values (199);commit;

五个活跃进程pid
1840251:18004:活跃事务一
1840253:18257:活跃事务二
1840255:18523:活跃事务三
1840256:19656:已提交事务一
1840257:24492:调试进程

【调试进程】执行begin;insert into a values (199) 获取快照,观察快照获取过程
begin;insert into a values (199);

在调试进程执行insert into a values (199);时会获取快照:

GetSnapshotData

代码语言:javascript
复制
Snapshot
GetSnapshotData(Snapshot snapshot)
{
	ProcArrayStruct *arrayP = procArray;
	TransactionId xmin;
	TransactionId xmax;
	TransactionId globalxmin;
	int			index;
	int			count = 0;
	int			subcount = 0;
	bool		suboverflowed = false;
	volatile TransactionId replication_slot_xmin = InvalidTransactionId;
	volatile TransactionId replication_slot_catalog_xmin = InvalidTransactionId;
  
  ...
  
	LWLockAcquire(ProcArrayLock, LW_SHARED);

上一个使用完的XID,按当前的事务状态:xid=1840256

代码语言:javascript
复制
	xmax = ShmemVariableCache->latestCompletedXid;
	TransactionIdAdvance(xmax);

xmax++:xmax=1840257

globalxmin初始化为1840257

代码语言:javascript
复制
	globalxmin = xmin = xmax;
  
  ...

	if (!snapshot->takenDuringRecovery)
	{
		int		   *pgprocnos = arrayP->pgprocnos;
		int			numProcs;

当前进程状态?

  1. 现在有numProcs个进程在allProcs中:numProcs=7
  2. 拿到allProcs数组index:p pgprocnos0、p pgprocnos1、。。。、p pgprocnos6:94、95、96、97、98、99、111
  3. 查看进程状态p allProcs94,p allPgXact94。。。

pgprocnos

pid

进程名

xid

xmin

94

24492

当前调试进程

1840257

0

95

19656

已提交事务一

0

0

96

18523

活跃事务三

1840255

0

97

18257

活跃事务二

1840253

0

98

2301

wal sender

0

0

99

18004

活跃事务一

1840251

0

111

2290

bgworker: logical replication launcher

0

0

当前

xmin

xmax

globalxmin

1840257

1840257

1840257

代码语言:javascript
复制
		/*
		 * Spin over procArray checking xid, xmin, and subxids.  The goal is
		 * to gather all active xids, find the lowest xmin, and try to record
		 * subxids.
		 */
		numProcs = arrayP->numProcs;
		for (index = 0; index < numProcs; index++)
		{
			int			pgprocno = pgprocnos[index];
			volatile PGXACT *pgxact = &allPgXact[pgprocno];
			TransactionId xid;
      
      ...

			/* Update globalxmin to be the smallest valid xmin */
			xid = pgxact->xmin; /* fetch just once */

如果xmin是有效的,记录最大的xmin到globalxmin中

代码语言:javascript
复制
			if (TransactionIdIsNormal(xid) &&
				NormalTransactionIdPrecedes(xid, globalxmin))
				globalxmin = xid;

			/* Fetch xid just once - see GetNewTransactionId */
			xid = pgxact->xid;

			/*
			 * If the transaction has no XID assigned, we can skip it; it
			 * won't have sub-XIDs either.  If the XID is >= xmax, we can also
			 * skip it; such transactions will be treated as running anyway
			 * (and any sub-XIDs will also be >= xmax).
			 */

xid = 1840257的第一轮循环会直接continue,为什么?xmax 现在也等于1840257,所以无论如何1840257都会算做运行中的事务。

这里两个条件无需继续遍历allxact:

  • 当前事务ID未分配:无需判断
  • 当前事务ID>=xmax:通过xmax可以知道当前事务活跃
代码语言:javascript
复制
			if (!TransactionIdIsNormal(xid)
				|| !NormalTransactionIdPrecedes(xid, xmax))
				continue;

			/*
			 * We don't include our own XIDs (if any) in the snapshot, but we
			 * must include them in xmin.
			 */

如果xid < xmin,需要记录最小的活跃事务ID,能查出来xid!=0就说明事务正在运行中。

xip中不记录当前进程的xid

代码语言:javascript
复制
			if (NormalTransactionIdPrecedes(xid, xmin))
				xmin = xid;
			if (pgxact == MyPgXact)
				continue;

			/* Add XID to snapshot. */
			snapshot->xip[count++] = xid;

记录子事务ID信息,子事务相关可以看这篇:

Postgresql源码(25)子事务可见性判断和性能问题

代码语言:javascript
复制
			if (!suboverflowed)
			{
				if (pgxact->overflowed)
					suboverflowed = true;
				else
				{
					int			nxids = pgxact->nxids;

					if (nxids > 0)
					{
						volatile PGPROC *proc = &allProcs[pgprocno];

						memcpy(snapshot->subxip + subcount,
							   (void *) proc->subxids.xids,
							   nxids * sizeof(TransactionId));
						subcount += nxids;
					}
				}
			}
		}
	}
	else
	{
		...
    // in hot standby
	}

循环总结:

  • globalxmin是pgxact->xmin中最小的值
  • xmin是pgxact->xid的最小值
  • xmax在循环内不变,一直是最大的事务ID++

循环结束时

xmin

xmax

globalxmin

1840251

1840257

1840257

代码语言:javascript
复制
  ...

(重要)

把全局最小的xmin更新到共享内存MyPgXact->xmin 和 TransactionXmin 和 globalxmin中。

TransactionIdPrecedes is <

代码语言:javascript
复制
	if (!TransactionIdIsValid(MyPgXact->xmin))
		MyPgXact->xmin = TransactionXmin = xmin;

	LWLockRelease(ProcArrayLock);

	if (TransactionIdPrecedes(xmin, globalxmin))
		globalxmin = xmin;

...

	RecentXmin = xmin;

	snapshot->xmin = xmin;
	snapshot->xmax = xmax;
	snapshot->xcnt = count;
	snapshot->subxcnt = subcount;
	snapshot->suboverflowed = suboverflowed;

	snapshot->curcid = GetCurrentCommandId(false);
	snapshot->active_count = 0;
	snapshot->regd_count = 0;
	snapshot->copied = false;

...

	return snapshot;
}

gdb

代码语言:javascript
复制
(gdb) p *snapshot
$34 = {satisfies = 0xa2f5bb <HeapTupleSatisfiesMVCC>, xmin = 1840251, xmax = 1840257, xip = 0x12c81c0, xcnt = 3, 
  subxip = 0x7fecacadc010, subxcnt = 0, suboverflowed = 0 '\000', takenDuringRecovery = 0 '\000', copied = 0 '\000', curcid = 1, 
  speculativeToken = 0, active_count = 0, regd_count = 0, ph_node = {first_child = 0x0, next_sibling = 0x0, prev_or_parent = 0x0}, 
  whenTaken = 0, lsn = 0}

流程总结

(不关注vacuum相关逻辑)

  • 快照获取一般会和PushActiveSnapshot函数组合使用,PushActiveSnapshot负责把快照CopySnapshot到TopTransactionContext中保存一段时间,用与长时间使用的场景,例如PortalRunMulti执行SQL(ProcessQuery)前会PushActiveSnapshot保存一个快照,ProcessQuery用完了在PortalRunMulti里面PopActiveSnapshot删除快照。
  • 步骤
代码语言:txt
复制
- 第一步:ProcArrayLock加读锁,在锁内通过xmax = ShmemVariableCache->latestCompletedXid拿到上一个使用过的xid,xmax=xid+1
- 第二步:遍历所有活跃进程,最小的xid记录到xmin中,最小的xmin记录到globalxmin中。
- 第三步:如果MyXact中的xmin无效,更新MyPgXact->xmin = TransactionXmin = xmin。(造成cache line invalid,性能问题点,针对这点PG13有大幅度优化)

性能问题

共享内存中的MyPgXact->xmin被频繁更新,导致cpu cache失效。

事务内的一次查询就会造成四次共享内存更新

代码语言:javascript
复制
exec_simple_query
  GetSnapshotData
    MyPgXact->xmin = 0 --> 1840251

exec_simple_query
  PopActiveSnapshot
    SnapshotResetXmin
      MyPgXact->xmin = 1840251 --> 0

exec_simple_query
  PortalRun
    PortalRunMulti 
      GetTransactionSnapshot
        GetSnapshotData
          MyPgXact->xmin = 0 --> 1840251

exec_simple_query
  PortalRun
    PortalRunMulti 
      PopActiveSnapshot
        SnapshotResetXmin
          MyPgXact->xmin = 1840251 --> 0

后面准备专门对这部分做一次性能分析。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 构造场景
  • GetSnapshotData
  • 流程总结
  • 性能问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档