PostgreSQL 源码解读(257)- PG 14(Improving connection scalability)#9

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

一、数据结构

N/A

二、源码解读

主要的修改仍是去掉RecentGlobalXmin的访问.

snapmgr.c

--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
  /*
    主要的修改:
      去掉RecentGlobalXmin的访问,改为更新GlobalVis*函数依赖的状态信息
  */
@@ -157,16 +157,9 @@ static Snapshot HistoricSnapshot = NULL;
  * These are updated by GetSnapshotData.  We initialize them this way
  * for the convenience of TransactionIdIsInProgress: even in bootstrap
  * mode, we don't want it to say that BootstrapTransactionId is in progress.
- *
- * RecentGlobalXmin and RecentGlobalDataXmin are initialized to
- * InvalidTransactionId, to ensure that no one tries to use a stale
- * value. Readers should ensure that it has been set to something else
- * before using it.
  */
 TransactionId TransactionXmin = FirstNormalTransactionId;
 TransactionId RecentXmin = FirstNormalTransactionId;
-TransactionId RecentGlobalXmin = InvalidTransactionId;
-TransactionId RecentGlobalDataXmin = InvalidTransactionId;
 /* (table, ctid) => (cmin, cmax) mapping during timetravel */
 static HTAB *tuplecid_data = NULL;
@@ -581,9 +574,7 @@ SetTransactionSnapshot(Snapshot sourcesnap, VirtualTransactionId *sourcevxid,
     * Even though we are not going to use the snapshot it computes, we must
     * call GetSnapshotData, for two reasons: (1) to be sure that
     * CurrentSnapshotData's XID arrays have been allocated, and (2) to update
-    * RecentXmin and RecentGlobalXmin.  (We could alternatively include those
-    * two variables in exported snapshot files, but it seems better to have
-    * snapshot importers compute reasonably up-to-date values for them.)
+    * the state for GlobalVis*.
     */
    CurrentSnapshot = GetSnapshotData(&CurrentSnapshotData);
@@ -956,36 +947,6 @@ xmin_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
        return 0;
 }
-/*
- * Get current RecentGlobalXmin value, as a FullTransactionId.
- */
-FullTransactionId
-GetFullRecentGlobalXmin(void)
-{
-   FullTransactionId nextxid_full;
-   uint32      nextxid_epoch;
-   TransactionId nextxid_xid;
-   uint32      epoch;
-
-   Assert(TransactionIdIsNormal(RecentGlobalXmin));
-
-   /*
-    * Compute the epoch from the next XID's epoch. This relies on the fact
-    * that RecentGlobalXmin must be within the 2 billion XID horizon from the
-    * next XID.
-    */
-   nextxid_full = ReadNextFullTransactionId();
-   nextxid_epoch = EpochFromFullTransactionId(nextxid_full);
-   nextxid_xid = XidFromFullTransactionId(nextxid_full);
-
-   if (RecentGlobalXmin > nextxid_xid)
-       epoch = nextxid_epoch - 1;
-   else
-       epoch = nextxid_epoch;
-
-   return FullTransactionIdFromEpochAndXid(epoch, RecentGlobalXmin);
-}
-
 /*
  * SnapshotResetXmin
  *
@@ -1753,106 +1714,157 @@ GetOldSnapshotThresholdTimestamp(void)
    return threshold_timestamp;
 }
-static void
+void
 SetOldSnapshotThresholdTimestamp(TimestampTz ts, TransactionId xlimit)
 {
    SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
+   Assert(oldSnapshotControl->threshold_timestamp <= ts);
+   Assert(TransactionIdPrecedesOrEquals(oldSnapshotControl->threshold_xid, xlimit));
    oldSnapshotControl->threshold_timestamp = ts;
    oldSnapshotControl->threshold_xid = xlimit;
    SpinLockRelease(&oldSnapshotControl->mutex_threshold);
 }
+/*
+ * XXX: Magic to keep old_snapshot_threshold tests appear "working". They
+ * currently are broken, and discussion of what to do about them is
+ * ongoing. See
+ * https://www.postgresql.org/message-id/20200403001235.e6jfdll3gh2ygbuc%40alap3.anarazel.de
+ */
+void
+SnapshotTooOldMagicForTest(void)
+{
+   TimestampTz ts = GetSnapshotCurrentTimestamp();
+
+   Assert(old_snapshot_threshold == 0);
+
+   ts -= 5 * USECS_PER_SEC;
+
+   SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
+   oldSnapshotControl->threshold_timestamp = ts;
+   SpinLockRelease(&oldSnapshotControl->mutex_threshold);
+}
+
+/*
+ * If there is a valid mapping for the timestamp, set *xlimitp to
+ * that. Returns whether there is such a mapping.
+ */
+static bool
+GetOldSnapshotFromTimeMapping(TimestampTz ts, TransactionId *xlimitp)
+{
+   bool        in_mapping = false;
+
+   Assert(ts == AlignTimestampToMinuteBoundary(ts));
+
+   LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED);
+
+   if (oldSnapshotControl->count_used > 0
+       && ts >= oldSnapshotControl->head_timestamp)
+   {
+       int         offset;
+
+       offset = ((ts - oldSnapshotControl->head_timestamp)
+                 / USECS_PER_MINUTE);
+       if (offset > oldSnapshotControl->count_used - 1)
+           offset = oldSnapshotControl->count_used - 1;
+       offset = (oldSnapshotControl->head_offset + offset)
+           % OLD_SNAPSHOT_TIME_MAP_ENTRIES;
+
+       *xlimitp = oldSnapshotControl->xid_by_minute[offset];
+
+       in_mapping = true;
+   }
+
+   LWLockRelease(OldSnapshotTimeMapLock);
+
+   return in_mapping;
+}
+
 /*
  * TransactionIdLimitedForOldSnapshots
  *
- * Apply old snapshot limit, if any.  This is intended to be called for page
- * pruning and table vacuuming, to allow old_snapshot_threshold to override
- * the normal global xmin value.  Actual testing for snapshot too old will be
- * based on whether a snapshot timestamp is prior to the threshold timestamp
- * set in this function.
+ * Apply old snapshot limit.  This is intended to be called for page pruning
+ * and table vacuuming, to allow old_snapshot_threshold to override the normal
+ * global xmin value.  Actual testing for snapshot too old will be based on
+ * whether a snapshot timestamp is prior to the threshold timestamp set in
+ * this function.
+ *
+ * If the limited horizon allows a cleanup action that otherwise would not be
+ * possible, SetOldSnapshotThresholdTimestamp(*limit_ts, *limit_xid) needs to
+ * be called before that cleanup action.
  */
  /*
    TransactionIdLimitedForOldSnapshots : 快照过旧的XID边界
    修改:
      取消全局xmin的访问
  */
-TransactionId
+bool
 TransactionIdLimitedForOldSnapshots(TransactionId recentXmin,
-                                   Relation relation)
+                                   Relation relation,
+                                   TransactionId *limit_xid,
+                                   TimestampTz *limit_ts)
 {
-   if (TransactionIdIsNormal(recentXmin)
-       && old_snapshot_threshold >= 0
-       && RelationAllowsEarlyPruning(relation))
-   {
-       TimestampTz ts = GetSnapshotCurrentTimestamp();
-       TransactionId xlimit = recentXmin;
-       TransactionId latest_xmin;
-       TimestampTz update_ts;
-       bool        same_ts_as_threshold = false;
+   TimestampTz ts;
+   TransactionId xlimit = recentXmin;
+   TransactionId latest_xmin;
+   TimestampTz next_map_update_ts;
+   TransactionId threshold_timestamp;
+   TransactionId threshold_xid;
-       SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin);
-       latest_xmin = oldSnapshotControl->latest_xmin;
-       update_ts = oldSnapshotControl->next_map_update;
-       SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin);
+   Assert(TransactionIdIsNormal(recentXmin));
+   Assert(OldSnapshotThresholdActive());
+   Assert(limit_ts != NULL && limit_xid != NULL);
-       /*
-        * Zero threshold always overrides to latest xmin, if valid.  Without
-        * some heuristic it will find its own snapshot too old on, for
-        * example, a simple UPDATE -- which would make it useless for most
-        * testing, but there is no principled way to ensure that it doesn't
-        * fail in this way.  Use a five-second delay to try to get useful
-        * testing behavior, but this may need adjustment.
-        */
-       if (old_snapshot_threshold == 0)
-       {
-           if (TransactionIdPrecedes(latest_xmin, MyPgXact->xmin)
-               && TransactionIdFollows(latest_xmin, xlimit))
-               xlimit = latest_xmin;
+   if (!RelationAllowsEarlyPruning(relation))
+       return false;
-           ts -= 5 * USECS_PER_SEC;
-           SetOldSnapshotThresholdTimestamp(ts, xlimit);
+   ts = GetSnapshotCurrentTimestamp();
-           return xlimit;
-       }
+   SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin);
+   latest_xmin = oldSnapshotControl->latest_xmin;
+   next_map_update_ts = oldSnapshotControl->next_map_update;
+   SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin);
+   /*
+    * Zero threshold always overrides to latest xmin, if valid.  Without some
+    * heuristic it will find its own snapshot too old on, for example, a
+    * simple UPDATE -- which would make it useless for most testing, but
+    * there is no principled way to ensure that it doesn't fail in this way.
+    * Use a five-second delay to try to get useful testing behavior, but this
+    * may need adjustment.
+    */
+   if (old_snapshot_threshold == 0)
+   {
+       if (TransactionIdPrecedes(latest_xmin, MyPgXact->xmin)
+           && TransactionIdFollows(latest_xmin, xlimit))
+           xlimit = latest_xmin;
+
+       ts -= 5 * USECS_PER_SEC;
+   }
+   else
+   {
        ts = AlignTimestampToMinuteBoundary(ts)
            - (old_snapshot_threshold * USECS_PER_MINUTE);
        /* Check for fast exit without LW locking. */
        SpinLockAcquire(&oldSnapshotControl->mutex_threshold);
-       if (ts == oldSnapshotControl->threshold_timestamp)
-       {
-           xlimit = oldSnapshotControl->threshold_xid;
-           same_ts_as_threshold = true;
-       }
+       threshold_timestamp = oldSnapshotControl->threshold_timestamp;
+       threshold_xid = oldSnapshotControl->threshold_xid;
        SpinLockRelease(&oldSnapshotControl->mutex_threshold);
-       if (!same_ts_as_threshold)
+       if (ts == threshold_timestamp)
+       {
+           /*
+            * Current timestamp is in same bucket as the the last limit that
+            * was applied. Reuse.
+            */
+           xlimit = threshold_xid;
+       }
+       else if (ts == next_map_update_ts)
+       {
+           /*
+            * FIXME: This branch is super iffy - but that should probably
+            * fixed separately.
+            */
+           xlimit = latest_xmin;
+       }
+       else if (GetOldSnapshotFromTimeMapping(ts, &xlimit))
        {
-           if (ts == update_ts)
-           {
-               xlimit = latest_xmin;
-               if (NormalTransactionIdFollows(xlimit, recentXmin))
-                   SetOldSnapshotThresholdTimestamp(ts, xlimit);
-           }
-           else
-           {
-               LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED);
-
-               if (oldSnapshotControl->count_used > 0
-                   && ts >= oldSnapshotControl->head_timestamp)
-               {
-                   int         offset;
-
-                   offset = ((ts - oldSnapshotControl->head_timestamp)
-                             / USECS_PER_MINUTE);
-                   if (offset > oldSnapshotControl->count_used - 1)
-                       offset = oldSnapshotControl->count_used - 1;
-                   offset = (oldSnapshotControl->head_offset + offset)
-                       % OLD_SNAPSHOT_TIME_MAP_ENTRIES;
-                   xlimit = oldSnapshotControl->xid_by_minute[offset];
-
-                   if (NormalTransactionIdFollows(xlimit, recentXmin))
-                       SetOldSnapshotThresholdTimestamp(ts, xlimit);
-               }
-
-               LWLockRelease(OldSnapshotTimeMapLock);
-           }
        }
        /*
@@ -1867,12 +1879,18 @@ TransactionIdLimitedForOldSnapshots(TransactionId recentXmin,
        if (TransactionIdIsNormal(latest_xmin)
            && TransactionIdPrecedes(latest_xmin, xlimit))
            xlimit = latest_xmin;
+   }
+
+   if (TransactionIdIsValid(xlimit) &&
+       TransactionIdFollowsOrEquals(xlimit, recentXmin))
+   {
+       *limit_ts = ts;
+       *limit_xid = xlimit;
-       if (NormalTransactionIdFollows(xlimit, recentXmin))
-           return xlimit;
+       return true;
    }
-   return recentXmin;
+   return false;
 }
 /*

三、跟踪分析

N/A

四、参考资料

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

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