PostgreSQL 源码解读(251)- PG 14(Improving connection scalability)#3

PostgreSQL 14在MVCC上面有所增强,以保证在大批量客户端连接场景下性能不会出现显著下降(详细可参见参考资料中的链接)。本节介绍了提交记录“Don’t compute global horizons while building snapshots.”中heapam_visibility.c中的修改以及涉及的相关数据结构。

一、数据结构

HTSV_Result


/* Result codes for HeapTupleSatisfiesVacuum */
/* HeapTupleSatisfiesVacuum返回的结果码 */
typedef enum
{
    HEAPTUPLE_DEAD,                /* 元组已死且可以被清除,tuple is dead and deletable */
    HEAPTUPLE_LIVE,                /* 元组存活,tuple is live (committed, no deleter) */
    HEAPTUPLE_RECENTLY_DEAD,    /* 元组已死,但不能被清除,tuple is dead, but not deletable yet */
    HEAPTUPLE_INSERT_IN_PROGRESS,    /* 插入事务仍在进行中,inserting xact is still in progress */
    HEAPTUPLE_DELETE_IN_PROGRESS    /* 删除事务仍在进行中,deleting xact is still in progress */
} HTSV_Result;

HTSV == HeapTupleSatisfiesVacuum首字母大写

二、源码解读

--- a/src/backend/access/heap/heapam_visibility.c
+++ b/src/backend/access/heap/heapam_visibility.c
@@ -1154,19 +1154,56 @@ HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot,
  * we mainly want to know is if a tuple is potentially visible to *any*
  * running transaction.  If so, it can't be removed yet by VACUUM.
  *
- * OldestXmin is a cutoff XID (obtained from GetOldestXmin()).  Tuples
- * deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
- * still be visible to some open transaction, so we can't remove them,
- * even if we see that the deleting transaction has committed.
+ * OldestXmin is a cutoff XID (obtained from
+ * GetOldestNonRemovableTransactionId()).  Tuples deleted by XIDs >=
+ * OldestXmin are deemed "recently dead"; they might still be visible to some
+ * open transaction, so we can't remove them, even if we see that the deleting
+ * transaction has committed.
  */
  /*
      设xID = 从GetOldestNonRemovableTransactionId
      对于所有 < xID 的事务IDs,可被VACUUM清除
      HeapTupleSatisfiesVacuum函数,
      调用HeapTupleSatisfiesVacuumHorizon判断元组是否“最近死亡-HEAPTUPLE_RECENTLY_DEAD”,
  */
 HTSV_Result
 HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
                         Buffer buffer)
+{
+   TransactionId dead_after = InvalidTransactionId;
+   HTSV_Result res;
+
+   res = HeapTupleSatisfiesVacuumHorizon(htup, buffer, &dead_after);
+
+   if (res == HEAPTUPLE_RECENTLY_DEAD)
+   {
+       Assert(TransactionIdIsValid(dead_after));
+
+       if (TransactionIdPrecedes(dead_after, OldestXmin))
+           res = HEAPTUPLE_DEAD;
+   }
+   else
+       Assert(!TransactionIdIsValid(dead_after));
+
+   return res;
+}
+
+/*
+ * Work horse for HeapTupleSatisfiesVacuum and similar routines.
+ *
+ * In contrast to HeapTupleSatisfiesVacuum this routine, when encountering a
+ * tuple that could still be visible to some backend, stores the xid that
+ * needs to be compared with the horizon in *dead_after, and returns
+ * HEAPTUPLE_RECENTLY_DEAD. The caller then can perform the comparison with
+ * the horizon.  This is e.g. useful when comparing with different horizons.
+ *
+ * Note: HEAPTUPLE_DEAD can still be returned here, e.g. if the inserting
+ * transaction aborted.
+ */
+HTSV_Result
+HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *dead_after)
 {
    HeapTupleHeader tuple = htup->t_data;
    Assert(ItemPointerIsValid(&htup->t_self));
    Assert(htup->t_tableOid != InvalidOid);
+   Assert(dead_after != NULL);
+
+   *dead_after = InvalidTransactionId;
    /*
     * Has inserting transaction committed?
@@ -1323,17 +1360,15 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
        else if (TransactionIdDidCommit(xmax))//xmax事务已提交,说明元组已被删除
        {
            /*
-            * The multixact might still be running due to lockers.  If the
-            * updater is below the xid horizon, we have to return DEAD
-            * regardless -- otherwise we could end up with a tuple where the
-            * updater has to be removed due to the horizon, but is not pruned
-            * away.  It's not a problem to prune that tuple, because any
-            * remaining lockers will also be present in newer tuple versions.
+            * The multixact might still be running due to lockers.  Need to
+            * allow for pruning if below the xid horizon regardless --
+            * otherwise we could end up with a tuple where the updater has to
+            * be removed due to the horizon, but is not pruned away.  It's
+            * not a problem to prune that tuple, because any remaining
+            * lockers will also be present in newer tuple versions.
             */
-           if (!TransactionIdPrecedes(xmax, OldestXmin))
-               return HEAPTUPLE_RECENTLY_DEAD;
-
-           return HEAPTUPLE_DEAD;
+           *dead_after = xmax;//返回xmax
+           return HEAPTUPLE_RECENTLY_DEAD;//状态:最近死亡
        }
        else if (!MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false))
        {
@@ -1372,14 +1407,11 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
    }
    /*
-    * Deleter committed, but perhaps it was recent enough that some open
-    * transactions could still see the tuple.
+    * Deleter committed, allow caller to check if it was recent enough that
+    * some open transactions could still see the tuple.
     删除事务已提交,但仍可能被其他活动事务可见
     */
-   if (!TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin))
-       return HEAPTUPLE_RECENTLY_DEAD;
-
-   /* Otherwise, it's dead and removable */
-   return HEAPTUPLE_DEAD;
+   *dead_after = HeapTupleHeaderGetRawXmax(tuple);//xmax
+   return HEAPTUPLE_RECENTLY_DEAD;//状态:最近死亡
 }
@@ -1393,14 +1425,28 @@ HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin,
  *
  * This is an interface to HeapTupleSatisfiesVacuum that's callable via
  * HeapTupleSatisfiesSnapshot, so it can be used through a Snapshot.
- * snapshot->xmin must have been set up with the xmin horizon to use.
+ * snapshot->vistest must have been set up with the horizon to use.
    snapshot->vistest必须设置
  */
 static bool
 HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
                                Buffer buffer)
 {
-   return HeapTupleSatisfiesVacuum(htup, snapshot->xmin, buffer)//原逻辑:调用HeapTupleSatisfiesVacuum判断
-       != HEAPTUPLE_DEAD;
+   TransactionId dead_after = InvalidTransactionId;
+   HTSV_Result res;
+
+   res = HeapTupleSatisfiesVacuumHorizon(htup, buffer, &dead_after);
    //新逻辑:调用HeapTupleSatisfiesVacuumHorizon获取是否最近死亡,
    //         若是,则进一步调用GlobalVisTestIsRemovableXid判断是否可被清除
+
+   if (res == HEAPTUPLE_RECENTLY_DEAD)//元组最近死亡
+   {
+       Assert(TransactionIdIsValid(dead_after));
+
+       if (GlobalVisTestIsRemovableXid(snapshot->vistest, dead_after))//可见性判断
+           res = HEAPTUPLE_DEAD;//可以被清除
+   }
+   else
+       Assert(!TransactionIdIsValid(dead_after));
+
+   return res != HEAPTUPLE_DEAD;//HEAPTUPLE_DEAD状态的元组才能被清除
 }
@@ -1418,7 +1464,7 @@ HeapTupleSatisfiesNonVacuumable(HeapTuple htup, Snapshot snapshot,
  * if the tuple is removable.
  */
 bool
-HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
+HeapTupleIsSurelyDead(HeapTuple htup, GlobalVisState *vistest)
 {
    HeapTupleHeader tuple = htup->t_data;
@@ -1459,7 +1505,8 @@ HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin)
        return false;
    /* Deleter committed, so tuple is dead if the XID is old enough. */
-   return TransactionIdPrecedes(HeapTupleHeaderGetRawXmax(tuple), OldestXmin);//原逻辑:元组xmax比OldestXmin要小则可清除,否则不能清除
+   return GlobalVisTestIsRemovableXid(vistest,
+                                      HeapTupleHeaderGetRawXmax(tuple));//新逻辑:调用GlobalVisTestXXX函数,确认元组是否可清除
 }
 /*

GlobalVisTestXXX函数在procarray.c文件中,后续再行介绍。

三、跟踪分析

N/A

四、参考资料

1. Improving connection scalability: GetSnapshotData()
2. snapshot scalability: Don’t compute global horizons while building snapshots.

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