前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql-xl全局快照代码走读与GTM原理(支线1)

Postgresql-xl全局快照代码走读与GTM原理(支线1)

作者头像
mingjie
发布2022-11-30 16:15:39
1.1K0
发布2022-11-30 16:15:39
举报
文章被收录于专栏:Postgresql源码分析

相关: 《Postgresql源码(18)PGPROC相关结构》 《Postgresql源码(65)新快照体系Globalvis工作原理分析》 《Postgresql快照优化Globalvis新体系分析(性能大幅增强)》 《Postgresql源码(23)Clog使用的Slru页面淘汰机制》

(这篇是PG视角看GTM、后面在总结一篇GTM内部逻辑)

(前面是一些概念,后面是GDB走读)

1 概念

1.1 集群MVCC

  • Postgres-xl基本上使用PG提供的xmin、xmax、clog、snapshot。xl只是扩展了PG的机制来分配事务 ID 并将快照提供给全局。
  • 当用户向cn发出 DML 语句时,cn从 GTM 获取全局事务 ID(GXID)和全局事务快照并将其发送到数据节点,dn 使用 GXID 和来自cn的快照来执行具体操作。通过这种方式,dn共享相同的事务上下文,并且当事务在多个cn和dn中运行时,它可以保持原子和统一的可见性。 在事务结束时,如果更新涉及多个节点,则协调器使用 2PC 协议隐式提交事务。 通过跟踪全局事务状态,协调器向 GTM 报告全局事务状态。

1.2 GTM通信时机

  • cn和GTM通信:
    • require new transaction ID
    • need new transaction snapshot
    • commit or abort transactions
  • dn和GTM通信:
    • vacuum

1.3 可见性判断

  • PG需要两个关键信息使可加性判断得到正确的结果
代码语言:txt
复制
- 运行中的事务:snapshot
- 非运行事务的状态:clog 或 元组标志位(shot path)xl的可见性判断需要GTM提供:
代码语言:txt
复制
- 需要有全局统一分发的事务ID
- 全局统一的快照
  • 每个事务在启动、结束、一阶段提交、二阶段提交都会通知GTM,让GTM获得全局信息,可以生成全局快照。

1.4 GTM交互

  • 如图所示,当cn开始一个新的事务时,它会向 GTM 请求新的事务 ID(GXID,global transaction id)。
  • 如果隔离界别为REPEATED READ,将获取快照并在整个事务中使用。
  • 如果隔离界别为READ COMMITTED ,每个语句重新从 GTM 获取快照。
  • 然后分析语句,确定要走的数据节点,并在必要时为每个数据节点进行转换。
  • 注意,语句将通过 GXID 和全局快照传递到适当的数据节点,以维护全局事务标识和行的可见性。
  • 在事务结束时,如果事务中的更新涉及多个dn,则协调器发出 PREPARE TRANSACTION for 2PC,然后发出 COMMIT。这些步骤也将报告给 GTM,以跟踪每个事务状态,以计算后续的全局快照。

1.5 GTM提供的上层接口

连接GTM

  • IsGTMConnected()
  • InitGTM():创建连接,保存连接信息到本地
  • CloseGTM()

获取全局事务ID

  • BeginTranGTM()
  • BeginTranAutovacuumGTM()

事务通知

  • CommitTranGTM():通知GTM事务提交
  • RollbackTranGTM():通知GTM事务回滚
  • StartPreparedTranGTM():通知GTM启动prepare
  • PrepareTranGTM():通知GTM完成prepare
  • CommitPreparedTranGTM():通知GTM二阶段提交

获取快照、GID

  • GetSnapshotGTM():获取全局快照
  • GetGIDDataGTM():获取两阶段的gid

2 Postgresql-xl对事务处理函数的修改

事务处理函数的基础功能请参考:《Postgresql源码(60)事务系统总结》,下面是pg-xl在分布式场景下对事务处理函数的修改。

2.1 StartTransaction

事务状态机函数:差异点在PGXC不支持可串行化的隔离级别。

2.2 CommitTransaction

  1. 如果cn涉及事务写操作,事务提交时会调用PrepareTransaction做一阶段提交。

perpare阶段:

  1. prepared后,cn会新起一个事务,使用和二阶段相同的GXID继续事务提交。
  2. cn会继续调用PreCommit_Remote以 2PC 方式将commit传播到dn。
  3. cn处理完成后,会调用FinishPreparedTransaction结束2PC。
  4. 然后CallGTMCallbacks调用回调函数通知GTM,例如全局序列管理器。

正常事务提交:

  1. RecordTransactionCommit写XLOG

结束:

  1. AtEOXact_GlobalTxn请求GTM提交事务。
  2. AtEOXact_Remote清理事务信息。

2.3 PrepareTransaction

  1. cn调用PrePrepare_Remote发送prepare命令到dn。
  2. cn调用CallGTMCallbacks通知GTM事务已经prepared。
  3. cn调用AtEOXact_GlobalTxn通知GTM可以prepare了。

2.4 AbortTransaction

  1. cn调用PreAbort_Remote取消dn上的事务。
  2. cn调用FinishPreparedTransaction清理本地事务。
  3. cn调用CallGTMCallbacks通知GTM事务已经回滚。
  4. cn调用AtEOXact_GlobalTxn通知GTM取消事务。
  5. cn调用AtEOXact_Remote清理。

3 Postgresql-xl快照数据结构对比

  • GTM_SnapshotData 和 SnapshotData 相似,GTM 在cn和dn之外管理相同的快照数据。
  • GTM 没有子事务数据,因为不支持子事务。
  • GTM 不需要存commandid ID 数据,因为启动事务的cn本地会存。 commandid 可以在cn中本地处理,无需 GTM 帮助。 如果在涉及的cn或其他cn上需要增加,它会通知cn后使用。
代码语言:javascript
复制
// PG
typedef struct SnapshotData
{
	SnapshotSatisfiesFunc satisfies;	/* tuple test function */
	TransactionId xmin;			/* all XID < xmin are visible to me */
	TransactionId xmax;			/* all XID >= xmax are invisible to me */
	TransactionId *xip;
	uint32		xcnt;			/* # of xact ids in xip[] */
#ifdef PGXC  /* PGXC_COORD */
	uint32		max_xcnt;		/* Max # of xact in xip[] */
#endif

	TransactionId *subxip;
	int32		subxcnt;		/* # of xact ids in subxip[] */
	bool		suboverflowed;	/* has the subxip array overflowed? */

	bool		takenDuringRecovery;	/* recovery-shaped snapshot? */
	bool		copied;			/* false if it's a static snapshot */

	CommandId	curcid;			/* in my xact, CID < curcid are visible */

	/*
	 * An extra return value for HeapTupleSatisfiesDirty, not used in MVCC
	 * snapshots.
	 */
	uint32		speculativeToken;

	/*
	 * Book-keeping information, used by the snapshot manager
	 */
	uint32		active_count;	/* refcount on ActiveSnapshot stack */
	uint32		regd_count;		/* refcount on RegisteredSnapshots */
	pairingheap_node ph_node;	/* link in the RegisteredSnapshots heap */

	TimestampTz whenTaken;		/* timestamp when snapshot was taken */
	XLogRecPtr	lsn;			/* position in the WAL stream when taken */
} SnapshotData;

// PGXL
typedef struct GTM_SnapshotData
{
	uint64					sn_snapid;
	GlobalTransactionId		sn_xmin;
	GlobalTransactionId		sn_xmax;
	uint32					sn_xcnt;
	GlobalTransactionId		*sn_xip;
} GTM_SnapshotData;

4【调试】CN快照获取

数据准备

代码语言:javascript
复制
-- cn1执行
psql -p50854 -h127.0.0.1 -Upgxc postgres
drop table clstr_tst;
CREATE TABLE clstr_tst (a SERIAL, b INT, c TEXT, d TEXT) DISTRIBUTE BY HASH (b);
INSERT INTO clstr_tst (b, c) VALUES (1, 'once');
INSERT INTO clstr_tst (b, c) VALUES (2, 'diez');
INSERT INTO clstr_tst (b, c) VALUES (3, 'treinta y uno');
INSERT INTO clstr_tst (b, c) VALUES (4, 'veintidos');
INSERT INTO clstr_tst (b, c) VALUES (5, 'tres');
INSERT INTO clstr_tst (b, c) VALUES (6, 'veinte');
INSERT INTO clstr_tst (b, c) VALUES (7, 'veintitres');

-- 数据分布
-- dn
psql -p50856 -h127.0.0.1 -Upgxc postgres -c 'select * from clstr_tst'
1
2
5
6
psql -p50857 -h127.0.0.1 -Upgxc postgres -c 'select * from clstr_tst'
3
4
7

调试

代码语言:javascript
复制
-- cn1执行
psql -p50854 -h127.0.0.1 -Upgxc postgres
begin;
update clstr_tst set c = 'updated' where b = 5;
select txid_current();
20215

-- cn2执行
psql -p50855 -h127.0.0.1 -Upgxc postgres
update clstr_tst set c = 'updated' where b = 7;
select txid_current();
20221

-- cn1调试

4.1 CN本地快照获取global_snapshot_source=coordinator

代码语言:javascript
复制
GetSnapshotData
    // 【1】用户可配快照获取方式,默认GTM,也可以生成本地快照,代价是全局一致性。
    if (GlobalSnapshotSource == GLOBAL_SNAPSHOT_SOURCE_GTM)
      if (GetPGXCSnapshotData(snapshot, latest))
        return snapshot;
    
    // 【2】本地快照生成
    for (index = 0; index < numProcs; index++)
      ...
      // 【3】读PGXACT->xmin
      xid = pgxact->xmin; /* fetch just once */
      if (TransactionIdIsNormal(xid) && NormalTransactionIdPrecedes(xid, globalxmin))
        globalxmin = xid;
      ...
      if (NormalTransactionIdPrecedes(xid, xmin))
      	xmin = xid;
    
 
     // 【4】循环结束快照的构建已经完毕,顺便更新PGXACT->xmin
     if (!TransactionIdIsValid(MyPgXact->xmin))
       MyPgXact->xmin = TransactionXmin = xmin;
     
     //【5】xmin参数修正后,当做全局做小可清理位点。
     ...
     RecentGlobalDataXmin = RecentGlobalXmin;

// cat /sys/devices/system/cpu/cpu1/cache/index0/coherency_line_size 
// 64

其中【4】、【3】会带来大量cacheline失效,导致高并发场景下性能急速下降:《Postgresql快照优化Globalvis新体系分析(性能大幅增强)》

4.2 CN远程快照获取global_snapshot_source=gtm

场景

代码语言:javascript
复制
s1: -----------begin(20251)---------------------------------------------
s2: ---------------------------------begin(20260)-----------------------
s3: --------------------------------------------------------debug---------
step1. GetSnapshotData
代码语言:javascript
复制
GetSnapshotData
    // 【1】用户可配快照获取方式,默认GTM,也可以生成本地快照,代价是全局一致性。
    if (GlobalSnapshotSource == GLOBAL_SNAPSHOT_SOURCE_GTM)
      if (GetPGXCSnapshotData(snapshot, latest))
        return snapshot;
step2. GetSnapshotDataFromGTM
代码语言:javascript
复制
GetPGXCSnapshotData(Snapshot snapshot, bool latest)
  GetSnapshotDataFromGTM(snapshot)
 
    // 读取ClusterMonitorCtl->reporting_recent_global_xmin
    reporting_xmin = ClusterMonitorGetReportingGlobalXmin();
    
    // GTM上层接口
    // {sn_snapid = 14190, sn_xmin = 20251, sn_xmax = 20260, sn_xcnt = 1, sn_xip = 0x13db6e0}
    gtm_snapshot = GetSnapshotGTM(GetCurrentTransactionIdIfAny(), canbe_grouped);
    
    // 拿快照顺便更新全局清理位点
    // ClusterMonitorCtl->gtm_recent_global_xmin = 20251
    RecentGlobalXmin = ClusterMonitorGetGlobalXmin(false);
    RecentGlobalDataXmin = RecentGlobalXmin;
    
    // 构造到全局快照
    SetGlobalSnapshotData(gtm_snapshot->sn_xmin, gtm_snapshot->sn_xmax,gtm_snapshot->sn_xcnt, gtm_snapshot->sn_xip, SNAPSHOT_DIRECT); 
    
    // 用全局快照构造PG快照
    GetSnapshotFromGlobalSnapshot(snapshot);  
      // 配置:snapshot->xmin
      // 配置:snapshot->xmax
      // 配置:snapshot->xcnt
      // 计算:global_xmin(PG是遍历PGXACT的xmin和xid,PGXL直接用ClusterMonitorCtl->gtm_recent_global_xmin)
      // 更新RecentGlobalXmin和RecentGlobalDataXmin
      
    // 更新ClusterMonitor
    ClusterMonitorSyncGlobalStateUsingSnapshot(gtm_snapshot);

关于globalxmin的计算:gtm_recent_global_xmin

cluster monitor process进程每隔5秒唤醒一次

代码语言:javascript
复制
while (!got_SIGTERM)
  ...
  oldestXmin = GetOldestXminInternal(NULL, 0, true, lastGlobalXmin);
  // 【重要】把自己看到的oldestXmin发给GTM,从GTM拿到全局最小newOldestXmin
  ReportGlobalXmin(oldestXmin, &newOldestXmin, &latestCompletedXid)));
  ClusterMonitorSetGlobalXmin(newOldestXmin)
    ...
    // 扩展CLOG,保证后面任何依赖RecentGlobalXmin的操作可以在CLOG正确拿到slot,下面具体介绍
    ExtendLogs(newOldestXmin);
    ...
    ClusterMonitorCtl->gtm_recent_global_xmin = newOldestXmin;

5 CLOG扩展

5.1 基础

《Postgresql源码(22)CLOG内存结构图示》

  • 32个lsn一组,一个页面8192字节,一个字节8位,2位可以表示一个事务状态,所以一个页面可以对应8192 * 4 个lsn。
  • 每32个一组,一个页面有1024组,每组记录最大lsn在group_lsn中。
  • 一个页面1024组,需要1024个uint64记录每组最大的lsn。
  • 内存连续申请,头部指针,尾部数据。中间控制信息。数组大小=页面个数
  • 页面个数=Min(128, Max(4, NBuffers / 512)),最大128个,最小4个。
  • 例如shared_buffers=128MB,NBuffers=16384,页面个数=32个。
  • CLOG中一个页面常称为SLOT。

5.2 PG扩展

PG单机:

代码语言:javascript
复制
void
ExtendCLOG(TransactionId newestXact)
{
	int			pageno;

	// newestXact % CLOG_XACTS_PER_PAGE,为0说明上一个用完了,需要扩展。
	if (TransactionIdToPgIndex(newestXact) != 0 &&
	    // ==3发生回卷,必须要扩展,
		!TransactionIdEquals(newestXact, FirstNormalTransactionId))
		return;

    // newestXact / CLOG_XACTS_PER_PAGE:计算slot位置。
	pageno = TransactionIdToPage(newestXact);

	LWLockAcquire(XactSLRULock, LW_EXCLUSIVE);

	ZeroCLOGPage(pageno, true);
	  
	  SimpleLruZeroPage 
	    // 选出一个slot : 《Postgresql源码(23)Clog使用的Slru页面淘汰机制》
	    SlruSelectLRUPage 
	    // 如果全部buffer都在使用,需要刷下去一个(依据是page_lru_count最小的那个)
	    SlruInternalWritePage
	  // 写一条CLOG_ZEROPAGE的XLOG
	  WriteZeroPageXlogRec

	LWLockRelease(XactSLRULock);
}

PGXL:

代码语言:javascript
复制
void
ExtendCLOG(TransactionId newestXact)
{
	int			pageno;
	// 由于事务ID可能在别的节点上申请,导致当前节点申请事务ID时,拿到是不连续的值。
	// PG原生的机制是连续的事务ID申请,切每次都调用ExtendCLOG。
	// 所以这里增加latestXid,记录上次一在当前节点使用的XID。
	TransactionId latestXid;
	
	// %
	pageno = TransactionIdToPage(newestXact);
	// 计算上一次在当前节点XID申请到哪了
	latestXid = (ClogCtl->shared->latest_page_number * CLOG_XACTS_PER_PAGE)
					+ CLOG_XACTS_PER_PAGE - 1;
    // 如果上一次申请到了10000,现在需要的xid5000,clog页面够用直接返回。
	if (TransactionIdPrecedesOrEquals(newestXact, latestXid))
		return;
    // 走到这里说明CLOG页面不够用了,但需要考虑竞争场景:
    
    // 拿上锁在检查一遍,其他进程可能并发的进行扩展,够了就不用再扩展了。
	LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);
	latestXid = (ClogCtl->shared->latest_page_number * CLOG_XACTS_PER_PAGE)
					+ CLOG_XACTS_PER_PAGE - 1;
	if (TransactionIdPrecedesOrEquals(newestXact, latestXid))
	{
		LWLockRelease(CLogControlLock);
		return;
	}
    
    // 确实不够了,那就从上一次申请的slot位置latest_page_number,连续+1的向后申请。
	for (;;)
	{
		/* Zero the page and make an XLOG entry about it */
		int target_pageno = ClogCtl->shared->latest_page_number + 1;
		if (target_pageno > TransactionIdToPage(MaxTransactionId))
			target_pageno = 0;
		ZeroCLOGPage(target_pageno, true);
		if (target_pageno == pageno)
			break;
	}
	LWLockRelease(CLogControlLock);
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 概念
    • 1.1 集群MVCC
      • 1.2 GTM通信时机
        • 1.3 可见性判断
          • 1.4 GTM交互
            • 1.5 GTM提供的上层接口
            • 2 Postgresql-xl对事务处理函数的修改
              • 2.1 StartTransaction
                • 2.2 CommitTransaction
                  • 2.3 PrepareTransaction
                    • 2.4 AbortTransaction
                    • 3 Postgresql-xl快照数据结构对比
                    • 4【调试】CN快照获取
                      • 4.1 CN本地快照获取global_snapshot_source=coordinator
                        • 4.2 CN远程快照获取global_snapshot_source=gtm
                          • step1. GetSnapshotData
                          • step2. GetSnapshotDataFromGTM
                      • 5 CLOG扩展
                        • 5.1 基础
                          • 5.2 PG扩展
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档