diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 1d8ca2429f9..6584a9cb8da 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -668,8 +668,8 @@ static void heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, Relation OldIndex, bool use_sort, TransactionId OldestXmin, - TransactionId FreezeXid, - MultiXactId MultiXactCutoff, + TransactionId *xid_cutoff, + MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) @@ -707,8 +707,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, isnull = (bool *) palloc(natts * sizeof(bool)); /* Initialize the rewrite operation */ - rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, FreezeXid, - MultiXactCutoff, use_wal); + rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, + *multi_cutoff, use_wal); /* Set up sorting if wanted */ diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 8dc76fa8583..9364cd4c33f 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -213,6 +213,10 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, Assert(params != NULL); Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT); + /* not every AM requires these to be valid, but heap does */ + Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)); + Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid)); + /* measure elapsed time iff autovacuum logging requires it */ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0) { diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 4f4be1efbfc..3ee70560476 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -870,19 +870,17 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, * FreezeXid will become the table's new relfrozenxid, and that mustn't go * backwards, so take the max. */ - if (TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) + if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) && + TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) FreezeXid = OldHeap->rd_rel->relfrozenxid; /* * MultiXactCutoff, similarly, shouldn't go backwards either. */ - if (MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) + if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) && + MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) MultiXactCutoff = OldHeap->rd_rel->relminmxid; - /* return selected values to caller */ - *pFreezeXid = FreezeXid; - *pCutoffMulti = MultiXactCutoff; - /* * Decide whether to use an indexscan or seqscan-and-optional-sort to scan * the OldHeap. We know how to use a sort to duplicate the ordering of a @@ -915,13 +913,19 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, /* * Hand of the actual copying to AM specific function, the generic code - * cannot know how to deal with visibility across AMs. + * cannot know how to deal with visibility across AMs. Note that this + * routine is allowed to set FreezeXid / MultiXactCutoff to different + * values (e.g. because the AM doesn't use freezing). */ table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort, - OldestXmin, FreezeXid, MultiXactCutoff, + OldestXmin, &FreezeXid, &MultiXactCutoff, &num_tuples, &tups_vacuumed, &tups_recently_dead); + /* return selected values to caller, get set as relfrozenxid/minmxid */ + *pFreezeXid = FreezeXid; + *pCutoffMulti = MultiXactCutoff; + /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */ NewHeap->rd_toastoid = InvalidOid; @@ -1118,9 +1122,9 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, /* set rel1's frozen Xid and minimum MultiXid */ if (relform1->relkind != RELKIND_INDEX) { - Assert(TransactionIdIsNormal(frozenXid)); + Assert(!TransactionIdIsValid(frozenXid) || + TransactionIdIsNormal(frozenXid)); relform1->relfrozenxid = frozenXid; - Assert(MultiXactIdIsValid(cutoffMulti)); relform1->relminmxid = cutoffMulti; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 1a7291d94bc..94fb6f26063 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1313,36 +1313,61 @@ vac_update_datfrozenxid(void) /* * Only consider relations able to hold unfrozen XIDs (anything else - * should have InvalidTransactionId in relfrozenxid anyway.) + * should have InvalidTransactionId in relfrozenxid anyway). */ if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW && classForm->relkind != RELKIND_TOASTVALUE) + { + Assert(!TransactionIdIsValid(classForm->relfrozenxid)); + Assert(!MultiXactIdIsValid(classForm->relminmxid)); continue; - - Assert(TransactionIdIsNormal(classForm->relfrozenxid)); - Assert(MultiXactIdIsValid(classForm->relminmxid)); + } /* + * Some table AMs might not need per-relation xid / multixid + * horizons. It therefore seems reasonable to allow relfrozenxid and + * relminmxid to not be set (i.e. set to their respective Invalid*Id) + * independently. Thus validate and compute horizon for each only if + * set. + * * If things are working properly, no relation should have a * relfrozenxid or relminmxid that is "in the future". However, such * cases have been known to arise due to bugs in pg_upgrade. If we * see any entries that are "in the future", chicken out and don't do - * anything. This ensures we won't truncate clog before those - * relations have been scanned and cleaned up. + * anything. This ensures we won't truncate clog & multixact SLRUs + * before those relations have been scanned and cleaned up. */ - if (TransactionIdPrecedes(lastSaneFrozenXid, classForm->relfrozenxid) || - MultiXactIdPrecedes(lastSaneMinMulti, classForm->relminmxid)) + + if (TransactionIdIsValid(classForm->relfrozenxid)) { - bogus = true; - break; + Assert(TransactionIdIsNormal(classForm->relfrozenxid)); + + /* check for values in the future */ + if (TransactionIdPrecedes(lastSaneFrozenXid, classForm->relfrozenxid)) + { + bogus = true; + break; + } + + /* determine new horizon */ + if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid)) + newFrozenXid = classForm->relfrozenxid; } - if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid)) - newFrozenXid = classForm->relfrozenxid; + if (MultiXactIdIsValid(classForm->relminmxid)) + { + /* check for values in the future */ + if (MultiXactIdPrecedes(lastSaneMinMulti, classForm->relminmxid)) + { + bogus = true; + break; + } - if (MultiXactIdPrecedes(classForm->relminmxid, newMinMulti)) - newMinMulti = classForm->relminmxid; + /* determine new horizon */ + if (MultiXactIdPrecedes(classForm->relminmxid, newMinMulti)) + newMinMulti = classForm->relminmxid; + } } /* we're done with pg_class */ diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 0976029e737..53c91d92778 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -3033,8 +3033,8 @@ relation_needs_vacanalyze(Oid relid, multiForceLimit = recentMulti - multixact_freeze_max_age; if (multiForceLimit < FirstMultiXactId) multiForceLimit -= FirstMultiXactId; - force_vacuum = MultiXactIdPrecedes(classForm->relminmxid, - multiForceLimit); + force_vacuum = MultiXactIdIsValid(classForm->relminmxid) && + MultiXactIdPrecedes(classForm->relminmxid, multiForceLimit); } *wraparound = force_vacuum; diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 6fbfcb96c98..c018a44267a 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -452,8 +452,8 @@ typedef struct TableAmRoutine Relation OldIndex, bool use_sort, TransactionId OldestXmin, - TransactionId FreezeXid, - MultiXactId MultiXactCutoff, + TransactionId *xid_cutoff, + MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead); @@ -1297,32 +1297,37 @@ table_relation_copy_data(Relation rel, RelFileNode newrnode) * Copy data from `OldHeap` into `NewHeap`, as part of a CLUSTER or VACUUM * FULL. * - * If `use_sort` is true, the table contents are sorted appropriate for - * `OldIndex`; if use_sort is false and OldIndex is not InvalidOid, the data - * is copied in that index's order; if use_sort is false and OidIndex is - * InvalidOid, no sorting is performed. + * Additional Input parameters: + * - use_sort - if true, the table contents are sorted appropriate for + * `OldIndex`; if false and OldIndex is not InvalidOid, the data is copied + * in that index's order; if false and OidIndex is InvalidOid, no sorting is + * performed + * - OidIndex - see use_sort + * - OldestXmin - computed by vacuum_set_xid_limits(), even when + * not needed for the relation's AM + * - *xid_cutoff - dito + * - *multi_cutoff - dito * - * OldestXmin, FreezeXid, MultiXactCutoff must be currently valid values for - * the table. - * - * *num_tuples, *tups_vacuumed, *tups_recently_dead will contain statistics - * computed while copying for the relation. Not all might make sense for every - * AM. + * Output parameters: + * - *xid_cutoff - rel's new relfrozenxid value, may be invalid + * - *multi_cutoff - rel's new relminmxid value, may be invalid + * - *tups_vacuumed - stats, for logging, if appropriate for AM + * - *tups_recently_dead - stats, for logging, if appropriate for AM */ static inline void table_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, Relation OldIndex, bool use_sort, TransactionId OldestXmin, - TransactionId FreezeXid, - MultiXactId MultiXactCutoff, + TransactionId *xid_cutoff, + MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) { OldHeap->rd_tableam->relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort, OldestXmin, - FreezeXid, MultiXactCutoff, + xid_cutoff, multi_cutoff, num_tuples, tups_vacuumed, tups_recently_dead); }