1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Centralize horizon determination for temp tables, fixing bug due to skew.

This fixes a bug in the edge case where, for a temp table, heap_page_prune()
can end up with a different horizon than heap_vacuum_rel(). Which can trigger
errors like "ERROR: cannot freeze committed xmax ...".

The bug was introduced due to interaction of a7212be8b9 "Set cutoff xmin more
aggressively when vacuuming a temporary table." with dc7420c2c9 "snapshot
scalability: Don't compute global horizons while building snapshots.".

The problem is caused by lazy_scan_heap() assuming that the only reason its
HeapTupleSatisfiesVacuum() call would return HEAPTUPLE_DEAD is if the tuple is
a HOT tuple, or if the tuple's inserting transaction has aborted since the
heap_page_prune() call. But after a7212be8b9 that was also possible in other
cases for temp tables, because heap_page_prune() uses a different visibility
test after dc7420c2c9.

The fix is fairly simple: Move the special case logic for temp tables from
vacuum_set_xid_limits() to the infrastructure introduced in dc7420c2c9. That
ensures that the horizon used for pruning is at least as aggressive as the one
used by lazy_scan_heap(). The concrete horizon used for temp tables is
slightly different than the logic in dc7420c2c9, but should always be as
aggressive as before (see comments).

A significant benefit to centralizing the logic procarray.c is that now the
more aggressive horizons for temp tables does not just apply to VACUUM but
also to e.g. HOT pruning and the nbtree killtuples logic.

Because isTopLevel is not needed by vacuum_set_xid_limits() anymore, I
undid the the related changes from a7212be8b9.

This commit also adds an isolation test ensuring that the more aggressive
vacuuming and pruning of temp tables keeps working.

Debugged-By: Amit Kapila <amit.kapila16@gmail.com>
Debugged-By: Tom Lane <tgl@sss.pgh.pa.us>
Debugged-By: Ashutosh Sharma <ashu.coek88@gmail.com>
Author: Andres Freund <andres@anarazel.de>
Discussion: https://postgr.es/m/20201014203103.72oke6hqywcyhx7s@alap3.anarazel.de
Discussion: https://postgr.es/m/20201015083735.derdzysdtqdvxshp@alap3.anarazel.de
This commit is contained in:
Andres Freund
2020-10-28 17:53:41 -07:00
parent 60a51c6b32
commit 94bc27b576
9 changed files with 545 additions and 74 deletions

View File

@ -67,13 +67,10 @@ typedef struct
} RelToCluster;
static void rebuild_relation(Relation OldHeap, Oid indexOid,
bool isTopLevel, bool verbose);
static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool isTopLevel, bool verbose,
bool *pSwapToastByContent,
TransactionId *pFreezeXid,
MultiXactId *pCutoffMulti);
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
@ -173,7 +170,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
table_close(rel, NoLock);
/* Do the job. */
cluster_rel(tableOid, indexOid, stmt->options, isTopLevel);
cluster_rel(tableOid, indexOid, stmt->options);
}
else
{
@ -222,8 +219,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
PushActiveSnapshot(GetTransactionSnapshot());
/* Do the job. */
cluster_rel(rvtc->tableOid, rvtc->indexOid,
stmt->options | CLUOPT_RECHECK,
isTopLevel);
stmt->options | CLUOPT_RECHECK);
PopActiveSnapshot();
CommitTransactionCommand();
}
@ -254,7 +250,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
* and error messages should refer to the operation as VACUUM not CLUSTER.
*/
void
cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
cluster_rel(Oid tableOid, Oid indexOid, int options)
{
Relation OldHeap;
bool verbose = ((options & CLUOPT_VERBOSE) != 0);
@ -404,7 +400,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
TransferPredicateLocksToHeapRelation(OldHeap);
/* rebuild_relation does all the dirty work */
rebuild_relation(OldHeap, indexOid, isTopLevel, verbose);
rebuild_relation(OldHeap, indexOid, verbose);
/* NB: rebuild_relation does table_close() on OldHeap */
@ -549,12 +545,11 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
*
* OldHeap: table to rebuild --- must be opened and exclusive-locked!
* indexOid: index to cluster by, or InvalidOid to rewrite in physical order.
* isTopLevel: should be passed down from ProcessUtility.
*
* NB: this routine closes OldHeap at the right time; caller should not.
*/
static void
rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
{
Oid tableOid = RelationGetRelid(OldHeap);
Oid tableSpace = OldHeap->rd_rel->reltablespace;
@ -582,7 +577,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
AccessExclusiveLock);
/* Copy the heap data into the new table in the desired order */
copy_table_data(OIDNewHeap, tableOid, indexOid, isTopLevel, verbose,
copy_table_data(OIDNewHeap, tableOid, indexOid, verbose,
&swap_toast_by_content, &frozenXid, &cutoffMulti);
/*
@ -733,8 +728,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
* *pCutoffMulti receives the MultiXactId used as a cutoff point.
*/
static void
copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool isTopLevel, bool verbose,
copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
bool *pSwapToastByContent, TransactionId *pFreezeXid,
MultiXactId *pCutoffMulti)
{
@ -832,7 +826,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
* Since we're going to rewrite the whole table anyway, there's no reason
* not to be aggressive about this.
*/
vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0, isTopLevel,
vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);

View File

@ -907,8 +907,7 @@ get_all_vacuum_rels(int options)
/*
* vacuum_set_xid_limits() -- compute oldestXmin and freeze cutoff points
*
* Input parameters are the target relation, applicable freeze age settings,
* and isTopLevel which should be passed down from ProcessUtility.
* Input parameters are the target relation, applicable freeze age settings.
*
* The output parameters are:
* - oldestXmin is the cutoff value used to distinguish whether tuples are
@ -934,7 +933,6 @@ vacuum_set_xid_limits(Relation rel,
int freeze_table_age,
int multixact_freeze_min_age,
int multixact_freeze_table_age,
bool isTopLevel,
TransactionId *oldestXmin,
TransactionId *freezeLimit,
TransactionId *xidFullScanLimit,
@ -950,53 +948,33 @@ vacuum_set_xid_limits(Relation rel,
MultiXactId mxactLimit;
MultiXactId safeMxactLimit;
if (RELATION_IS_LOCAL(rel) && !IsInTransactionBlock(isTopLevel))
{
/*
* If we are processing a temp relation (which by prior checks must be
* one belonging to our session), and we are not inside any
* transaction block, then there can be no tuples in the rel that are
* still in-doubt, nor can there be any that are dead but possibly
* still interesting to some snapshot our session holds. We don't
* need to care whether other sessions could see such tuples, either.
* So we can aggressively set the cutoff xmin to be the nextXid.
*/
*oldestXmin = ReadNewTransactionId();
}
else
{
/*
* Otherwise, calculate the cutoff xmin normally.
*
* We can always ignore processes running lazy vacuum. This is
* because we use these values only for deciding which tuples we must
* keep in the tables. Since lazy vacuum doesn't write its XID
* anywhere (usually no XID assigned), it's safe to ignore it. In
* theory it could be problematic to ignore lazy vacuums in a full
* vacuum, but keep in mind that only one vacuum process can be
* working on a particular table at any time, and that each vacuum is
* always an independent transaction.
*/
*oldestXmin = GetOldestNonRemovableTransactionId(rel);
/*
* We can always ignore processes running lazy vacuum. This is because we
* use these values only for deciding which tuples we must keep in the
* tables. Since lazy vacuum doesn't write its XID anywhere (usually no
* XID assigned), it's safe to ignore it. In theory it could be
* problematic to ignore lazy vacuums in a full vacuum, but keep in mind
* that only one vacuum process can be working on a particular table at
* any time, and that each vacuum is always an independent transaction.
*/
*oldestXmin = GetOldestNonRemovableTransactionId(rel);
if (OldSnapshotThresholdActive())
if (OldSnapshotThresholdActive())
{
TransactionId limit_xmin;
TimestampTz limit_ts;
if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel,
&limit_xmin, &limit_ts))
{
TransactionId limit_xmin;
TimestampTz limit_ts;
if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel,
&limit_xmin, &limit_ts))
{
/*
* TODO: We should only set the threshold if we are pruning on
* the basis of the increased limits. Not as crucial here as
* it is for opportunistic pruning (which often happens at a
* much higher frequency), but would still be a significant
* improvement.
*/
SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
*oldestXmin = limit_xmin;
}
/*
* TODO: We should only set the threshold if we are pruning on the
* basis of the increased limits. Not as crucial here as it is
* for opportunistic pruning (which often happens at a much higher
* frequency), but would still be a significant improvement.
*/
SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
*oldestXmin = limit_xmin;
}
}
@ -1930,7 +1908,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
cluster_rel(relid, InvalidOid, cluster_options, true);
cluster_rel(relid, InvalidOid, cluster_options);
}
else
table_relation_vacuum(onerel, params, vac_strategy);