diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 1ecc8330851..8f1c11a9350 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -7089,6 +7089,12 @@ FreezeMultiXactId(MultiXactId multi, uint16 t_infomask, * process this tuple as part of freezing its page, and return true. Return * false if nothing can be changed about the tuple right now. * + * FreezePageConflictXid is advanced only for xmin/xvac freezing, not for xmax + * changes. We only remove xmax state here when it is lock-only, or when the + * updater XID (including an updater member of a MultiXact) must be aborted; + * otherwise, the tuple would already be removable. Neither case affects + * visibility on a standby. + * * Also sets *totally_frozen to true if the tuple will be totally frozen once * caller executes returned freeze plan (or if the tuple was already totally * frozen by an earlier VACUUM). This indicates that there are no remaining @@ -7164,7 +7170,11 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, /* Verify that xmin committed if and when freeze plan is executed */ if (freeze_xmin) + { frz->checkflags |= HEAP_FREEZE_CHECK_XMIN_COMMITTED; + if (TransactionIdFollows(xid, pagefrz->FreezePageConflictXid)) + pagefrz->FreezePageConflictXid = xid; + } } /* @@ -7183,6 +7193,9 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, */ replace_xvac = pagefrz->freeze_required = true; + if (TransactionIdFollows(xid, pagefrz->FreezePageConflictXid)) + pagefrz->FreezePageConflictXid = xid; + /* Will set replace_xvac flags in freeze plan below */ } @@ -7492,6 +7505,7 @@ heap_freeze_tuple(HeapTupleHeader tuple, pagefrz.freeze_required = true; pagefrz.FreezePageRelfrozenXid = FreezeLimit; pagefrz.FreezePageRelminMxid = MultiXactCutoff; + pagefrz.FreezePageConflictXid = InvalidTransactionId; pagefrz.NoFreezePageRelfrozenXid = FreezeLimit; pagefrz.NoFreezePageRelminMxid = MultiXactCutoff; diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 65c9f393f41..8748fa882e9 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -377,6 +377,7 @@ prune_freeze_setup(PruneFreezeParams *params, /* initialize page freezing working state */ prstate->pagefrz.freeze_required = false; + prstate->pagefrz.FreezePageConflictXid = InvalidTransactionId; if (prstate->attempt_freeze) { Assert(new_relfrozen_xid && new_relmin_mxid); @@ -407,7 +408,6 @@ prune_freeze_setup(PruneFreezeParams *params, * PruneState. */ prstate->deadoffsets = presult->deadoffsets; - prstate->frz_conflict_horizon = InvalidTransactionId; /* * Vacuum may update the VM after we're done. We can keep track of @@ -746,22 +746,8 @@ heap_page_will_freeze(bool did_tuple_hint_fpi, * critical section. */ heap_pre_freeze_checks(prstate->buffer, prstate->frozen, prstate->nfrozen); - - /* - * Calculate what the snapshot conflict horizon should be for a record - * freezing tuples. We can use the visibility_cutoff_xid as our cutoff - * for conflicts when the whole page is eligible to become all-frozen - * in the VM once we're done with it. Otherwise, we generate a - * conservative cutoff by stepping back from OldestXmin. - */ - if (prstate->set_all_frozen) - prstate->frz_conflict_horizon = prstate->visibility_cutoff_xid; - else - { - /* Avoids false conflicts when hot_standby_feedback in use */ - prstate->frz_conflict_horizon = prstate->cutoffs->OldestXmin; - TransactionIdRetreat(prstate->frz_conflict_horizon); - } + Assert(TransactionIdPrecedes(prstate->pagefrz.FreezePageConflictXid, + prstate->cutoffs->OldestXmin)); } else if (prstate->nfrozen > 0) { @@ -952,18 +938,18 @@ heap_page_prune_and_freeze(PruneFreezeParams *params, /* * The snapshotConflictHorizon for the whole record should be the * most conservative of all the horizons calculated for any of the - * possible modifications. If this record will prune tuples, any - * transactions on the standby older than the youngest xmax of the - * most recently removed tuple this record will prune will - * conflict. If this record will freeze tuples, any transactions - * on the standby with xids older than the youngest tuple this - * record will freeze will conflict. + * possible modifications. If this record will prune tuples, any + * queries on the standby older than the newest xid of the most + * recently removed tuple this record will prune will conflict. If + * this record will freeze tuples, any queries on the standby with + * xids older than the newest tuple this record will freeze will + * conflict. */ TransactionId conflict_xid; - if (TransactionIdFollows(prstate.frz_conflict_horizon, + if (TransactionIdFollows(prstate.pagefrz.FreezePageConflictXid, prstate.latest_xid_removed)) - conflict_xid = prstate.frz_conflict_horizon; + conflict_xid = prstate.pagefrz.FreezePageConflictXid; else conflict_xid = prstate.latest_xid_removed; diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index 24a27cc043a..ad993c07311 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -208,6 +208,18 @@ typedef struct HeapPageFreeze TransactionId FreezePageRelfrozenXid; MultiXactId FreezePageRelminMxid; + /* + * Newest XID that this page's freeze actions will remove from tuple + * visibility metadata (currently xmin and/or xvac). It is used to derive + * the snapshot conflict horizon for a WAL record that freezes tuples. On + * a standby, we must not replay that change while any snapshot could + * still treat that XID as running. + * + * It's only used if we execute freeze plans for this page, so there is no + * corresponding "no freeze" tracker. + */ + TransactionId FreezePageConflictXid; + /* * "No freeze" NewRelfrozenXid/NewRelminMxid trackers. *