当前位置:网站首页>Postgresql源码(65)新快照体系Globalvis工作原理分析
Postgresql源码(65)新快照体系Globalvis工作原理分析
2022-07-27 18:49:00 【mingjie73】
相关:
《Postgresql源码(18)PGPROC相关结构》
《Postgresql源码(65)新快照体系Globalvis工作原理分析》
《Postgresql快照优化Globalvis新体系分析(性能大幅增强)》
《Improving Postgres Connection Scalability: Snapshots》
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->pgprocnos[0]]
p allProcs[procArray->pgprocnos[1]]
p allProcs[procArray->pgprocnos[2]]
p ProcGlobal->xids[0]
p ProcGlobal->xids[1]
p ProcGlobal->xids[2]
2 GetSnapshotData第一部分——循环拿活跃事务
总结:
- 遍历
ProcGlobal->xids数组,拿到所有的xid(优化一:紧凑数组)。 - 活跃的xid添加到快照的xip数组中。
- 顺便算一个xmin(函数栈变量)记录最小的活跃事务id。
读取PGXACT->xmin(优化二)- xmax在进入循环前就算好了,一定是latestCompletedXid+1。
- 最后如果MyProc的xmin是无效的,更新一次
PGPROC->xmin(还是要更新自己的xmin,这是必须的,无优化) - 整个过程包在ProcArrayLock大锁中。
代码流程:
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是不能清理的。
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
测试场景:
事务一RC-----------4000440启动-------------------4000440结束------------------------------->
事务二RR----------------------4000450启动-----------------------------4000450结束---------->
事务三RC--------------------------------------------调试位置------------------------------->
调试位置:
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中间的值怎么知道有没有提交?
看下面函数:
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会有什么影响?
- 循环PGPROC时,看不到别的进程的xmin了,例如,一个RR事务A在拿快照的时候,最小事务ID:10还没提交,那么这个RR快照的xmin就是10。
- 优化前,A事务后面新事务B构造快照时,能通过A的PGPROC看到xmin=10,并更新自己的xmin=10。
- 优化后,A事务后面新事务B构造快照时,只能看到自己遍历时最小的xid,例如这时10已经提交了,B看到的是15,那么后构造的快照就会认为全局最小xmin=15,但是由于A的xmin是10,所以这时全局清理位点需要是10,不能是15。
边栏推荐
- Leetcode daily practice - the penultimate node in the linked list
- 建筑云渲染的应用正在扩大,越来越多的行业急需可视化服务
- R language uses LM function to build multiple linear regression model, writes regression equation according to model coefficient, and uses deviance function to calculate the sum of squares of residual
- Dobot magician robot arm - Introduction
- Common ArrayList interview questions
- Leetcode daily practice - 203. remove linked list elements
- Win11 widget prompts how to solve the error when loading this content?
- Paper appreciation [aaai18] meta multi task learning for sequence modeling
- Rust variable characteristics
- The dplyr package of R language performs aggregation transformations of data packets and calculates the sum of packets of dataframe data
猜你喜欢

Knowledge management system promotes the development of enterprise informatization

Good driving, inexpensive, comfortable and safe! Experience BYD song Pro DM-I in depth

Mysql 回表、SQL优化、四种隔离级别、三大日志binlog、redo log、undo log

Pytest失败重跑

A lock faster than read-write lock. Don't get to know it quickly

Leetcode daily practice - the penultimate node in the linked list

Conquer 3 pieces of IT equipment for all programmers →

sscanf 导致地址越界

Airiot Q & A issue 6 | how to use the secondary development engine?

Automatic classification of e-commerce UGC pictures using Baidu PaddlePaddle easydl
随机推荐
Elk too heavy? Try KFC log collection
使用百度飞桨EasyDL实现电商UGC图片自动分类
Simple use of express web server
数字化工厂系统有什么现实优势
PHP code audit 6 - file contains vulnerability
论文赏析[EMNLP18]针对自顶向下和中序移进归约成分句法分析的Dynamic Oracles
PHP代码审计5—XSS漏洞
Obtain website shell permission based on file upload vulnerability
Knife4j dynamically refreshes global parameters through JS
Tips for file upload to bypass WAF
MAPGIS 3D scene rendering technology and Application
Win11系统更新KB5014668后点开始按钮没反应怎么办?
R language uses dplyr package to perform data aggregation statistics, calculate sliding window statistics, calculate sliding group mean, and merge the generated statistical data into the original data
数字引领 规划先行 聚焦智慧规划信息平台建设及应用项目探索实践
When accessing the shared folder, you will be prompted "because file sharing is not secure smb1 Protocol". Please use Smb2 protocol
激光雷达中国前装大幕开启,数百万颗产能待消化
R language uses LROC function of epidisplay package to visualize ROC curve of logistic regression model and output diagnostic table, visualize multiple ROC curves, and use legend function to add legen
Leetcode daily practice 206. Reverse the linked list
AIRIOT答疑第6期|如何使用二次开发引擎?
Hexagon_ V65_ Programmers_ Reference_ Manual(7)