diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 809061110c7..bc9360b9743 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -805,16 +805,15 @@ heap_vacuum_rel(Relation rel, const VacuumParams params, * vacuum's ability to freeze and remove dead tuples. Since OldestXmin * already covers the slot xmin/catalog_xmin values, pass it as a * preliminary check to avoid additional iteration over all the slots. - * - * If at least one slot was invalidated, recompute cutoffs so that this - * vacuum benefits from the advanced horizon immediately. - * - * XXX: Next XID could be returned as output from vacuum_get_cutoffs() but - * for now we live with an additional ReadNextTransactionId() call. */ - if (InvalidateXIDAgedReplicationSlots(vacrel->cutoffs.OldestXmin, - ReadNextTransactionId())) + if (MaybeInvalidateXIDAgedSlots(vacrel->cutoffs.OldestXmin)) + { + /* + * Some slots have been invalidated based on their XID age; recompute + * the vacuum cutoffs. + */ vacrel->aggressive = vacuum_get_cutoffs(rel, params, &vacrel->cutoffs); + } vacrel->rel_pages = orig_rel_pages = RelationGetNumberOfBlocks(rel); vacrel->vistest = GlobalVisTestFor(rel); diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 8ca5d86fe0e..8296856eefe 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -160,7 +160,7 @@ int max_replication_slots = 10; /* the maximum number of replication int idle_replication_slot_timeout_secs = 0; /* - * Invalidate replication slots that have xmin or catalog_xmin greater + * Invalidate replication slots that have xmin or catalog_xmin older * than the specified age; '0' disables it. */ int max_slot_xid_age = 0; @@ -183,8 +183,6 @@ static XLogRecPtr ss_oldest_flush_lsn = InvalidXLogRecPtr; static void ReplicationSlotShmemExit(int code, Datum arg); static bool IsSlotForConflictCheck(const char *name); static void ReplicationSlotDropPtr(ReplicationSlot *slot); -static bool IsReplicationSlotXIDAged(TransactionId xmin, TransactionId catalog_xmin, - TransactionId nextXID); /* internal persistency functions */ static void RestoreSlotFromDisk(const char *name); @@ -1792,7 +1790,7 @@ ReportSlotInvalidation(ReplicationSlotInvalidationCause cause, long slot_idle_seconds, TransactionId xmin, TransactionId catalog_xmin, - TransactionId nextXID) + TransactionId recentXid) { StringInfoData err_detail; StringInfoData err_hint; @@ -1845,20 +1843,14 @@ ReportSlotInvalidation(ReplicationSlotInvalidationCause cause, if (TransactionIdIsValid(xmin)) { /* translator: %s is a GUC variable name */ - appendStringInfo(&err_detail, _("The slot's xmin %u at next transaction ID %u exceeds the age %d specified by \"%s\"."), - xmin, - nextXID, - max_slot_xid_age, - "max_slot_xid_age"); + appendStringInfo(&err_detail, _("The slot's xmin %u is %d transactions old, which exceeds the configured \"%s\" value of %d."), + xmin, (int32) (recentXid - xmin), "max_slot_xid_age", max_slot_xid_age); } else { /* translator: %s is a GUC variable name */ - appendStringInfo(&err_detail, _("The slot's catalog xmin %u at next transaction ID %u exceeds the age %d specified by \"%s\"."), - catalog_xmin, - nextXID, - max_slot_xid_age, - "max_slot_xid_age"); + appendStringInfo(&err_detail, _("The slot's xmin %u is %d transactions old, which exceeds the configured \"%s\" value of %d."), + catalog_xmin, (int32) (recentXid - catalog_xmin), "max_slot_xid_age", max_slot_xid_age); } /* translator: %s is a GUC variable name */ @@ -1905,6 +1897,25 @@ CanInvalidateIdleSlot(ReplicationSlot *s) !(RecoveryInProgress() && s->data.synced)); } +/* + * Can we invalidate an XID-aged replication slot? + * + * XID-aged based invalidation is allowed to the given slot when: + * + * 1. Max XID-age is set + * 2. Slot has valid xmin or catalog_xmin + * 3. The slot is not being synced from the primary while the server is in + * recovery. + */ +static inline bool +CanInvalidateXidAgedSlot(ReplicationSlot *s) +{ + return (max_slot_xid_age != 0 && + (TransactionIdIsValid(s->data.xmin) || + TransactionIdIsValid(s->data.catalog_xmin)) && + !(RecoveryInProgress() && s->data.synced)); +} + /* * DetermineSlotInvalidationCause - Determine the cause for which a slot * becomes invalid among the given possible causes. @@ -1916,7 +1927,7 @@ static ReplicationSlotInvalidationCause DetermineSlotInvalidationCause(uint32 possible_causes, ReplicationSlot *s, XLogRecPtr oldestLSN, Oid dboid, TransactionId snapshotConflictHorizon, - TransactionId nextXID, + TransactionId recentXid, TimestampTz *inactive_since, TimestampTz now) { Assert(possible_causes != RS_INVAL_NONE); @@ -1989,9 +2000,23 @@ DetermineSlotInvalidationCause(uint32 possible_causes, ReplicationSlot *s, } /* Check if the slot needs to be invalidated due to max_slot_xid_age GUC */ - if ((possible_causes & RS_INVAL_XID_AGE) && - IsReplicationSlotXIDAged(s->data.xmin, s->data.catalog_xmin, nextXID)) - return RS_INVAL_XID_AGE; + if (possible_causes & RS_INVAL_XID_AGE) + { + Assert(TransactionIdIsValid(recentXid)); + + if (CanInvalidateXidAgedSlot(s)) + { + TransactionId xidLimit; + + xidLimit = TransactionIdRetreatedBy(recentXid, max_slot_xid_age); + + if ((TransactionIdIsValid(s->data.xmin) && + TransactionIdPrecedes(s->data.xmin, xidLimit)) || + (TransactionIdIsValid(s->data.catalog_xmin) && + TransactionIdPrecedes(s->data.catalog_xmin, xidLimit))) + return RS_INVAL_XID_AGE; + } + } return RS_INVAL_NONE; } @@ -2015,7 +2040,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, ReplicationSlot *s, XLogRecPtr oldestLSN, Oid dboid, TransactionId snapshotConflictHorizon, - TransactionId nextXID, + TransactionId recentXid, bool *released_lock_out) { int last_signaled_pid = 0; @@ -2068,7 +2093,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, s, oldestLSN, dboid, snapshotConflictHorizon, - nextXID, + recentXid, &inactive_since, now); @@ -2163,7 +2188,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, slotname, restart_lsn, oldestLSN, snapshotConflictHorizon, slot_idle_secs, s->data.xmin, - s->data.catalog_xmin, nextXID); + s->data.catalog_xmin, recentXid); if (MyBackendType == B_STARTUP) (void) SignalRecoveryConflict(GetPGProcByNumber(active_proc), @@ -2217,7 +2242,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, slotname, restart_lsn, oldestLSN, snapshotConflictHorizon, slot_idle_secs, s->data.xmin, - s->data.catalog_xmin, nextXID); + s->data.catalog_xmin, recentXid); /* done with this slot for now */ break; @@ -2259,7 +2284,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, bool InvalidateObsoleteReplicationSlots(uint32 possible_causes, XLogSegNo oldestSegno, Oid dboid, - TransactionId snapshotConflictHorizon, TransactionId nextXID) + TransactionId snapshotConflictHorizon, + TransactionId recentXid) { XLogRecPtr oldestLSN; bool invalidated = false; @@ -2298,7 +2324,7 @@ restart: if (InvalidatePossiblyObsoleteSlot(possible_causes, s, oldestLSN, dboid, snapshotConflictHorizon, - nextXID, &released_lock)) + recentXid, &released_lock)) { Assert(released_lock); @@ -3330,104 +3356,53 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn) ConditionVariableCancelSleep(); } -/* - * Check if the passed-in xmin or catalog_xmin have aged beyond the - * max_slot_xid_age GUC limit relative to nextXID. - * - * Returns true if either value exceeds the configured age. - */ -static bool -IsReplicationSlotXIDAged(TransactionId xmin, TransactionId catalog_xmin, - TransactionId nextXID) -{ - TransactionId cutoffXID; - bool aged = false; - - if (max_slot_xid_age == 0) - return false; - - if (!TransactionIdIsNormal(nextXID)) - return false; - - cutoffXID = nextXID - max_slot_xid_age; - - /* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */ - /* this can cause the limit to go backwards by 3, but that's OK */ - if (cutoffXID < FirstNormalTransactionId) - cutoffXID -= FirstNormalTransactionId; - - if (TransactionIdIsNormal(xmin) && - TransactionIdPrecedes(xmin, cutoffXID)) - aged = true; - - if (TransactionIdIsNormal(catalog_xmin) && - TransactionIdPrecedes(catalog_xmin, cutoffXID)) - aged = true; - - return aged; -} - /* * Invalidate replication slots whose XID age exceeds the max_slot_xid_age * GUC. * - * The caller supplies oldestXmin, either computed via - * GetOldestNonRemovableTransactionId during vacuum, or computed via the - * minimum of slot xmin values obtained from ProcArrayGetReplicationSlotXmin, - * and nextXID, the next XID to be assigned used to compute the age. - * - * Preliminary checks based on the passed-in oldestXmin and the oldest slot - * xmin and catalog_xmin are done to avoid unnecessarily iterating over all - * the slots. If the oldestXmin age does not exceed the GUC then no - * individual slot can either, so the per-slot scan is skipped. For example, - * if oldestXmin is 100 and the GUC is 500, every slot's xmin must be >= 100, - * so none can be older than the GUC. Similarly, if the oldest slot xmin and - * catalog_xmin from ProcArray are not aged, the per-slot scan is skipped; - * this can happen when a long-running transaction holds the oldestXmin back. - * - * Even if the caller passes an oldestXmin that does not include the slot - * xmin/catalog_xmin range, there is no risk of incorrect invalidation: each - * slot's own xmin and catalog_xmin are individually verified against the GUC - * inside IsReplicationSlotXIDAged(). The only downside is an additional - * iteration over all the slots. + * The oldestXmin is expected to be a XID computed via + * GetOldestNonRemovableTransactionId() during vacuum. It is used as cutoffs + * for individual slot checks; if its age does not exceed the max_slot_xid_age, + * no individual slot can either, so we skip per-slot invalidation check. * * Returns true if at least one slot was invalidated. */ bool -InvalidateXIDAgedReplicationSlots(TransactionId oldestXmin, TransactionId nextXID) +MaybeInvalidateXIDAgedSlots(TransactionId oldestXmin) { - TransactionId cutoffXID; + TransactionId recentXid; + TransactionId xidLimit; + TransactionId slot_xmin = InvalidTransactionId; + TransactionId slot_catalog_xmin = InvalidTransactionId; bool invalidated = false; - if (max_slot_xid_age == 0) - return false; + Assert(TransactionIdIsValid(oldestXmin)); - if (!TransactionIdIsNormal(oldestXmin) || !TransactionIdIsNormal(nextXID)) + if (max_slot_xid_age == 0) return false; - cutoffXID = nextXID - max_slot_xid_age; + recentXid = ReadNextTransactionId(); + xidLimit = TransactionIdRetreatedBy(recentXid, max_slot_xid_age); - /* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */ - /* this can cause the limit to go backwards by 3, but that's OK */ - if (!TransactionIdIsNormal(cutoffXID)) - cutoffXID -= FirstNormalTransactionId; - - if (TransactionIdPrecedes(oldestXmin, cutoffXID)) - { - TransactionId slot_xmin; - TransactionId slot_catalog_xmin; + /* oldestXmin is not behind the cutoff; no need to check slots */ + if (TransactionIdPrecedes(xidLimit, oldestXmin)) + return false; - ProcArrayGetReplicationSlotXmin(&slot_xmin, &slot_catalog_xmin); + ProcArrayGetReplicationSlotXmin(&slot_xmin, &slot_catalog_xmin); - if (IsReplicationSlotXIDAged(slot_xmin, slot_catalog_xmin, nextXID)) - { - invalidated = InvalidateObsoleteReplicationSlots(RS_INVAL_XID_AGE, - 0, - InvalidOid, - InvalidTransactionId, - nextXID); - } - } + /* + * Invalidate possibly obsolete slots based on XID-age, if either slot's + * xmin or catalog_xmin is older than the cutoff. + */ + if ((TransactionIdIsValid(slot_xmin) && + TransactionIdPrecedes(slot_xmin, xidLimit)) || + (TransactionIdIsValid(slot_catalog_xmin) && + TransactionIdPrecedes(slot_catalog_xmin, xidLimit))) + invalidated = InvalidateObsoleteReplicationSlots(RS_INVAL_XID_AGE, + 0, + InvalidOid, + InvalidTransactionId, + recentXid); return invalidated; } diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index 83cf8438724..c483f0f531b 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -371,7 +371,8 @@ extern bool InvalidateObsoleteReplicationSlots(uint32 possible_causes, XLogSegNo oldestSegno, Oid dboid, TransactionId snapshotConflictHorizon, - TransactionId nextXID); + TransactionId recentXid); +extern bool MaybeInvalidateXIDAgedSlots(TransactionId oldestXmin); extern ReplicationSlot *SearchNamedReplicationSlot(const char *name, bool need_lock); extern int ReplicationSlotIndex(ReplicationSlot *slot); extern bool ReplicationSlotName(int index, Name name); @@ -391,6 +392,4 @@ extern bool SlotExistsInSyncStandbySlots(const char *slot_name); extern bool StandbySlotsHaveCaughtup(XLogRecPtr wait_for_lsn, int elevel); extern void WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn); -extern bool InvalidateXIDAgedReplicationSlots(TransactionId oldestXmin, TransactionId nextXID); - #endif /* SLOT_H */