PostgreSQL 源码解读(253)- PG 14(Improving connection scalability)#5

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.

请使用浏览器的分享功能分享到微信等