mirror of https://github.com/postgres/postgres
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:
parent
db864c3c36
commit
a7212be8b9
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue