summaryrefslogtreecommitdiff
path: root/src/backend/utils/time/snapmgr.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/time/snapmgr.c')
-rw-r--r--src/backend/utils/time/snapmgr.c250
1 files changed, 134 insertions, 116 deletions
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index 6b6c8571e23..604d823f686 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -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);
@@ -957,36 +948,6 @@ xmin_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
}
/*
- * 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
*
* If there are no more snapshots, we can reset our PGXACT->xmin to InvalidXid.
@@ -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.
*/
-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;
}
/*