Move various prechecks from vacuum() into ExecVacuum()

vacuum() is used for both the VACUUM command and for autovacuum. There
were many prechecks being done inside vacuum() that were just not relevant
to autovacuum.  Let's move the bulk of these into ExecVacuum() so that
they're only executed when running the VACUUM command.  This removes a
small amount of overhead when autovacuum vacuums a table.

While we are at it, allocate VACUUM's BufferAccessStrategy in ExecVacuum()
and pass it into vacuum() instead of expecting vacuum() to make it if it's
not already made by the calling function.  To make this work, we need to
create the vacuum memory context slightly earlier, so we now need to pass
that down to vacuum() so that it's available for use in other memory
allocations.

Author: Melanie Plageman
Reviewed-by: David Rowley
Discussion: https://postgr.es/m/20230405211534.4skgskbilnxqrmxg@awork3.anarazel.de
This commit is contained in:
David Rowley 2023-04-06 15:44:52 +12:00
parent acab1b0914
commit b9b125b9c1
3 changed files with 91 additions and 78 deletions

View File

@ -105,6 +105,7 @@ void
ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
VacuumParams params;
BufferAccessStrategy bstrategy = NULL;
bool verbose = false;
bool skip_locked = false;
bool analyze = false;
@ -115,6 +116,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool process_toast = true;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
ListCell *lc;
/* index_cleanup and truncate values unspecified for now */
@ -254,6 +256,42 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
}
}
/*
* Sanity check DISABLE_PAGE_SKIPPING option.
*/
if ((params.options & VACOPT_FULL) != 0 &&
(params.options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/* sanity check for PROCESS_TOAST */
if ((params.options & VACOPT_FULL) != 0 &&
(params.options & VACOPT_PROCESS_TOAST) == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PROCESS_TOAST required with VACUUM FULL")));
/* sanity check for ONLY_DATABASE_STATS */
if (params.options & VACOPT_ONLY_DATABASE_STATS)
{
Assert(params.options & VACOPT_VACUUM);
if (vacstmt->rels != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
/* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
if (params.options & ~(VACOPT_VACUUM |
VACOPT_VERBOSE |
VACOPT_PROCESS_MAIN |
VACOPT_PROCESS_TOAST |
VACOPT_ONLY_DATABASE_STATS))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
}
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
@ -279,12 +317,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */
params.log_min_duration = -1;
/*
* Create special memory context for cross-transaction storage.
*
* Since it is a child of PortalContext, it will go away eventually even
* if we suffer an error; there's no need for special abort cleanup logic.
*/
vac_context = AllocSetContextCreate(PortalContext,
"Vacuum",
ALLOCSET_DEFAULT_SIZES);
/*
* Make a buffer strategy object in the cross-transaction memory context.
* We needn't bother making this for VACUUM (FULL) or VACUUM
* (ONLY_DATABASE_STATS) as they'll not make use of it. VACUUM (FULL,
* ANALYZE) is possible, so we'd better ensure that we make a strategy
* when we see ANALYZE.
*/
if ((params.options & (VACOPT_ONLY_DATABASE_STATS |
VACOPT_FULL)) == 0 ||
(params.options & VACOPT_ANALYZE) != 0)
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
/* Now go through the common routine */
vacuum(vacstmt->rels, &params, NULL, isTopLevel);
vacuum(vacstmt->rels, &params, bstrategy, vac_context, isTopLevel);
/* Finally, clean up the vacuum memory context */
MemoryContextDelete(vac_context);
}
/*
* Internal entry point for VACUUM and ANALYZE commands.
* Internal entry point for autovacuum and the VACUUM / ANALYZE commands.
*
* relations, if not NIL, is a list of VacuumRelation to process; otherwise,
* we process all relevant tables in the database. For each VacuumRelation,
@ -294,8 +363,11 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
* params contains a set of parameters that can be used to customize the
* behavior.
*
* bstrategy is normally given as NULL, but in autovacuum it can be passed
* in to use the same buffer strategy object across multiple vacuum() calls.
* bstrategy may be passed in as NULL when the caller does not want to
* restrict the number of shared_buffers that VACUUM / ANALYZE can use,
* otherwise, the caller must build a BufferAccessStrategy with the number of
* shared_buffers that VACUUM / ANALYZE should try to limit themselves to
* using.
*
* isTopLevel should be passed down from ProcessUtility.
*
@ -303,12 +375,11 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
* memory context that will not disappear at transaction commit.
*/
void
vacuum(List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel)
vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy,
MemoryContext vac_context, bool isTopLevel)
{
static bool in_vacuum = false;
MemoryContext vac_context;
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
@ -344,69 +415,6 @@ vacuum(List *relations, VacuumParams *params,
errmsg("%s cannot be executed from VACUUM or ANALYZE",
stmttype)));
/*
* Sanity check DISABLE_PAGE_SKIPPING option.
*/
if ((params->options & VACOPT_FULL) != 0 &&
(params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
/* sanity check for PROCESS_TOAST */
if ((params->options & VACOPT_FULL) != 0 &&
(params->options & VACOPT_PROCESS_TOAST) == 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("PROCESS_TOAST required with VACUUM FULL")));
/* sanity check for ONLY_DATABASE_STATS */
if (params->options & VACOPT_ONLY_DATABASE_STATS)
{
Assert(params->options & VACOPT_VACUUM);
if (relations != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
/* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
if (params->options & ~(VACOPT_VACUUM |
VACOPT_VERBOSE |
VACOPT_PROCESS_MAIN |
VACOPT_PROCESS_TOAST |
VACOPT_ONLY_DATABASE_STATS))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
}
/*
* Create special memory context for cross-transaction storage.
*
* Since it is a child of PortalContext, it will go away eventually even
* if we suffer an error; there's no need for special abort cleanup logic.
*/
vac_context = AllocSetContextCreate(PortalContext,
"Vacuum",
ALLOCSET_DEFAULT_SIZES);
/*
* If caller didn't give us a buffer strategy object, make one in the
* cross-transaction memory context. We needn't bother making this for
* VACUUM (FULL) or VACUUM (ONLY_DATABASE_STATS) as they'll not make use
* of it. VACUUM (FULL, ANALYZE) is possible, so we'd better ensure that
* we make a strategy when we see ANALYZE.
*/
if (bstrategy == NULL &&
((params->options & (VACOPT_ONLY_DATABASE_STATS |
VACOPT_FULL)) == 0 ||
(params->options & VACOPT_ANALYZE) != 0))
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
/*
* Build list of relation(s) to process, putting any new data in
* vac_context for safekeeping.
@ -578,12 +586,6 @@ vacuum(List *relations, VacuumParams *params,
vac_update_datfrozenxid();
}
/*
* Clean up working storage --- note we must do this after
* StartTransactionCommand, else we might be trying to delete the active
* context!
*/
MemoryContextDelete(vac_context);
}
/*

View File

@ -3140,6 +3140,9 @@ relation_needs_vacanalyze(Oid relid,
/*
* autovacuum_do_vac_analyze
* Vacuum and/or analyze the specified table
*
* We expect the caller to have switched into a memory context that won't
* disappear at transaction commit.
*/
static void
autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
@ -3147,6 +3150,7 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
RangeVar *rangevar;
VacuumRelation *rel;
List *rel_list;
MemoryContext vac_context;
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
@ -3156,7 +3160,13 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
rel_list = list_make1(rel);
vacuum(rel_list, &tab->at_params, bstrategy, true);
vac_context = AllocSetContextCreate(CurrentMemoryContext,
"Vacuum",
ALLOCSET_DEFAULT_SIZES);
vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true);
MemoryContextDelete(vac_context);
}
/*

View File

@ -310,7 +310,8 @@ extern PGDLLIMPORT int VacuumCostBalanceLocal;
/* in commands/vacuum.c */
extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
extern void vacuum(List *relations, VacuumParams *params,
BufferAccessStrategy bstrategy, bool isTopLevel);
BufferAccessStrategy bstrategy, MemoryContext vac_context,
bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);