Use the newest to-be-frozen xid as the conflict horizon for freezing

Previously WAL records that froze tuples used OldestXmin as the snapshot
conflict horizon, or the visibility cutoff if the page would become
all-frozen. Both are newer than (or equal to) the newst XID actually
frozen on the page.

Track the newest XID that will be frozen and use that as the snapshot
conflict horizon instead. This yields an older horizon resulting in
fewer query cancellations on standbys.

Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Peter Geoghegan <pg@bowt.ie>
Discussion: https://postgr.es/m/CAAKRu_bbaUV8OUjAfVa_iALgKnTSfB4gO3jnkfpcFgrxEpSGJQ%40mail.gmail.com
master
Melanie Plageman 2 days ago
parent ac58465e06
commit c2a23dcf9e
  1. 14
      src/backend/access/heap/heapam.c
  2. 36
      src/backend/access/heap/pruneheap.c
  3. 12
      src/include/access/heapam.h

@ -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;

@ -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;

@ -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.
*

Loading…
Cancel
Save