Set cutoff xmin more aggressively when vacuuming a temporary table.

Since other sessions aren't allowed to look into a temporary table
of our own session, we do not need to worry about the global xmin
horizon when setting the vacuum XID cutoff.  Indeed, if we're not
inside a transaction block, we may set oldestXmin to be the next
XID, because there cannot be any in-doubt tuples in a temp table,
nor any tuples that are dead but still visible to some snapshot of
our transaction.  (VACUUM, of course, is never inside a transaction
block; but we need to test that because CLUSTER shares the same code.)

This approach allows us to always clean out a temp table completely
during VACUUM, independently of concurrent activity.  Aside from
being useful in its own right, that simplifies building reproducible
test cases.

Discussion: https://postgr.es/m/3490536.1598629609@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2020-09-01 18:37:12 -04:00
parent db864c3c36
commit a7212be8b9
5 changed files with 70 additions and 36 deletions

View File

@ -471,6 +471,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
params->freeze_table_age, params->freeze_table_age,
params->multixact_freeze_min_age, params->multixact_freeze_min_age,
params->multixact_freeze_table_age, params->multixact_freeze_table_age,
true, /* we must be a top-level command */
&OldestXmin, &FreezeLimit, &xidFullScanLimit, &OldestXmin, &FreezeLimit, &xidFullScanLimit,
&MultiXactCutoff, &mxactFullScanLimit); &MultiXactCutoff, &mxactFullScanLimit);

View File

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

View File

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

View File

@ -19,7 +19,8 @@
extern void cluster(ClusterStmt *stmt, bool isTopLevel); extern void cluster(ClusterStmt *stmt, bool isTopLevel);
extern void cluster_rel(Oid tableOid, Oid indexOid, int options); extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
bool isTopLevel);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck, LOCKMODE lockmode); bool recheck, LOCKMODE lockmode);
extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);

View File

@ -267,6 +267,7 @@ extern void vacuum_set_xid_limits(Relation rel,
int freeze_min_age, int freeze_table_age, int freeze_min_age, int freeze_table_age,
int multixact_freeze_min_age, int multixact_freeze_min_age,
int multixact_freeze_table_age, int multixact_freeze_table_age,
bool isTopLevel,
TransactionId *oldestXmin, TransactionId *oldestXmin,
TransactionId *freezeLimit, TransactionId *freezeLimit,
TransactionId *xidFullScanLimit, TransactionId *xidFullScanLimit,