PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接)。本节介绍了提交记录“Don’t compute global horizons while building snapshots.”中vacuumlazy.c、indexam.c、nbtpage.c、nbtree.c、nbtxlog.c和spgvacuum.c中的修改以及涉及的相关数据结构。
一、数据结构
N/A
二、源码解读
vacuumlazy.c
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -788,6 +788,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
/*
lazy_scan_heap函数做的事情是清理heap中的每个page,还包括截断死亡元组为死亡行指针,重整page和设置提交状态位,
使用空闲空间构建死亡元组和page链表,计算heap中的存活元组数,如需要则标记pages为全可见状态。
修改:
在调用heap_page_prune清理page时,换成新的heap_page_prune函数,不再依赖OldestXmin
*/
PROGRESS_VACUUM_MAX_DEAD_TUPLES
};
int64 initprog_val[3];
//使用GlobalVisXXX替换OldestXmin
+ GlobalVisState *vistest;
pg_rusage_init(&ru0);
@@ -816,6 +817,8 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
vacrelstats->nonempty_pages = 0;
vacrelstats->latestRemovedXid = InvalidTransactionId;
//使用GlobalVisXXX替换OldestXmin
+ vistest = GlobalVisTestFor(onerel);
+
/*
* Initialize state for a parallel vacuum. As of now, only one worker can
* be used for an index, so we invoke parallelism only if there are at
@@ -1239,7 +1242,8 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
*
* We count tuples removed by the pruning step as removed by VACUUM.
*/
//使用新的heap_page_prune函数替换原函数(带OldestXmin参数)
- tups_vacuumed += heap_page_prune(onerel, buf, OldestXmin, false,
+ tups_vacuumed += heap_page_prune(onerel, buf, vistest, false,
+ InvalidTransactionId, 0,
&vacrelstats->latestRemovedXid);
/*
@@ -1596,14 +1600,16 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
}
/*
- * It's possible for the value returned by GetOldestXmin() to move
- * backwards, so it's not wrong for us to see tuples that appear to
- * not be visible to everyone yet, while PD_ALL_VISIBLE is already
- * set. The real safe xmin value never moves backwards, but
- * GetOldestXmin() is conservative and sometimes returns a value
- * that's unnecessarily small, so if we see that contradiction it just
- * means that the tuples that we think are not visible to everyone yet
- * actually are, and the PD_ALL_VISIBLE flag is correct.
+ * It's possible for the value returned by
+ * GetOldestNonRemovableTransactionId() to move backwards, so it's not
+ * wrong for us to see tuples that appear to not be visible to
+ * everyone yet, while PD_ALL_VISIBLE is already set. The real safe
+ * xmin value never moves backwards, but
+ * GetOldestNonRemovableTransactionId() is conservative and sometimes
+ * returns a value that's unnecessarily small, so if we see that
+ * contradiction it just means that the tuples that we think are not
+ * visible to everyone yet actually are, and the PD_ALL_VISIBLE flag
+ * is correct.
*
* There should never be dead tuples on a page with PD_ALL_VISIBLE
* set, however.
indexam.c
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -519,7 +519,8 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
/*
index_getnext_tid : 该函数用于获取下一个TID
修改:
去掉变量RecentGlobalXmin,改为RecentXmin
*/
SCAN_CHECKS;
CHECK_SCAN_PROCEDURE(amgettuple);
- Assert(TransactionIdIsValid(RecentGlobalXmin));
+ /* XXX: we should assert that a snapshot is pushed or registered */
+ Assert(TransactionIdIsValid(RecentXmin));
/*
* The AM's amgettuple proc finds the next index entry matching the scan
nbtpage.c
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -1097,7 +1097,7 @@ _bt_page_recyclable(Page page)
/*
_bt_page_recyclable : 用于判断现存的page是否可回收?
*/
*/
opaque = (BTPageOpaque) PageGetSpecialPointer(page);
if (P_ISDELETED(opaque) &&
//使用GlobalVisXXX函数替换RecentGlobalXmin
- TransactionIdPrecedes(opaque->btpo.xact, RecentGlobalXmin))
+ GlobalVisCheckRemovableXid(NULL, opaque->btpo.xact))
return true;
return false;
}
@@ -2318,7 +2318,7 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
* updated links to the target, ReadNewTransactionId() suffices as an
* upper bound. Any scan having retained a now-stale link is advertising
* in its PGXACT an xmin less than or equal to the value we read here. It
- * will continue to do so, holding back RecentGlobalXmin, for the duration
+ * will continue to do so, holding back the xmin horizon, for the duration
* of that scan.
*/
page = BufferGetPage(buf);
nbtree
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -808,6 +808,12 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
/*
_bt_vacuum_needs_cleanup : 检查索引是否需要清理
*/
metapg = BufferGetPage(metabuf);
metad = BTPageGetMeta(metapg);
+ /*
+ * XXX: If IndexVacuumInfo contained the heap relation, we could be more
+ * aggressive about vacuuming non catalog relations by passing the table
+ * to GlobalVisCheckRemovableXid().
+ */
+
if (metad->btm_version < BTREE_NOVAC_VERSION)
{
/*
@@ -817,13 +823,12 @@ _bt_vacuum_needs_cleanup(IndexVacuumInfo *info)
result = true;
}
else if (TransactionIdIsValid(metad->btm_oldest_btpo_xact) &&
//使用GlobalVisXXX函数替换RecentGlobalXmin
- TransactionIdPrecedes(metad->btm_oldest_btpo_xact,
- RecentGlobalXmin))
+ GlobalVisCheckRemovableXid(NULL, metad->btm_oldest_btpo_xact))
{
/*
* If any oldest btpo.xact from a previously deleted page in the index
- * is older than RecentGlobalXmin, then at least one deleted page can
- * be recycled -- don't skip cleanup.
+ * is visible to everyone, then at least one deleted page can be
+ * recycled -- don't skip cleanup.
*/
result = true;
}
@@ -1276,14 +1281,13 @@ backtrack:
* own conflict now.)
*
* Backends with snapshots acquired after a VACUUM starts but
- * before it finishes could have a RecentGlobalXmin with a
- * later xid than the VACUUM's OldestXmin cutoff. These
- * backends might happen to opportunistically mark some index
- * tuples LP_DEAD before we reach them, even though they may
- * be after our cutoff. We don't try to kill these "extra"
- * index tuples in _bt_delitems_vacuum(). This keep things
- * simple, and allows us to always avoid generating our own
- * conflicts.
+ * before it finishes could have visibility cutoff with a
+ * later xid than VACUUM's OldestXmin cutoff. These backends
+ * might happen to opportunistically mark some index tuples
+ * LP_DEAD before we reach them, even though they may be after
+ * our cutoff. We don't try to kill these "extra" index
+ * tuples in _bt_delitems_vacuum(). This keep things simple,
+ * and allows us to always avoid generating our own conflicts.
*/
Assert(!BTreeTupleIsPivot(itup));
if (!BTreeTupleIsPosting(itup))
nbtxlog.c
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -948,11 +948,11 @@ btree_xlog_reuse_page(XLogReaderState *record)
/*
这里只是简单的改了注释而已
*/
* Btree reuse_page records exist to provide a conflict point when we
* reuse pages in the index via the FSM. That's all they do though.
*
- * latestRemovedXid was the page's btpo.xact. The btpo.xact <
- * RecentGlobalXmin test in _bt_page_recyclable() conceptually mirrors the
- * pgxact->xmin > limitXmin test in GetConflictingVirtualXIDs().
- * Consequently, one XID value achieves the same exclusion effect on
- * primary and standby.
+ * latestRemovedXid was the page's btpo.xact. The
+ * GlobalVisCheckRemovableXid test in _bt_page_recyclable() conceptually
+ * mirrors the pgxact->xmin > limitXmin test in
+ * GetConflictingVirtualXIDs(). Consequently, one XID value achieves the
+ * same exclusion effect on primary and standby.
*/
if (InHotStandby)
{
spgvacuum.c
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -501,10 +501,14 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
/*
vacuumRedirectAndPlaceholder : 清理给定page中的redirect和placeholder元组
修改:
同样的,使用GlobalVisXXX函数代替RecentGlobalXmin
*/
OffsetNumber itemToPlaceholder[MaxIndexTuplesPerPage];
OffsetNumber itemnos[MaxIndexTuplesPerPage];
spgxlogVacuumRedirect xlrec;
+ GlobalVisState *vistest;
xlrec.nToPlaceholder = 0;
xlrec.newestRedirectXid = InvalidTransactionId;
+ /* XXX: providing heap relation would allow more pruning */
+ vistest = GlobalVisTestFor(NULL);
+
START_CRIT_SECTION();
/*
@@ -521,7 +525,7 @@ vacuumRedirectAndPlaceholder(Relation index, Buffer buffer)
dt = (SpGistDeadTuple) PageGetItem(page, PageGetItemId(page, i));
if (dt->tupstate == SPGIST_REDIRECT &&
- TransactionIdPrecedes(dt->xid, RecentGlobalXmin))
+ GlobalVisTestIsRemovableXid(vistest, dt->xid))
{
dt->tupstate = SPGIST_PLACEHOLDER;
Assert(opaque->nRedirection > 0);
三、跟踪分析
N/A
四、参考资料
1.
Improving connection scalability: GetSnapshotData()
2.
snapshot scalability: Don’t compute global horizons while building snapshots.