aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2023-01-06 14:17:25 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2023-01-06 14:17:25 -0500
commita46a7011b27188af526047a111969f257aaf4db8 (patch)
tree816e22b0b77bcc10da44ed043eef2879615bc399 /src/backend
parentcd4b2334db4980bbf86a8ba1d446db17e62ca342 (diff)
downloadpostgresql-a46a7011b27188af526047a111969f257aaf4db8.tar.gz
postgresql-a46a7011b27188af526047a111969f257aaf4db8.zip
Add options to control whether VACUUM runs vac_update_datfrozenxid.
VACUUM normally ends by running vac_update_datfrozenxid(), which requires a scan of pg_class. Therefore, if one attempts to vacuum a database one table at a time --- as vacuumdb has done since v12 --- we will spend O(N^2) time in vac_update_datfrozenxid(). That causes serious performance problems in databases with tens of thousands of tables, and indeed the effect is measurable with only a few hundred. To add insult to injury, only one process can run vac_update_datfrozenxid at the same time per DB, so this behavior largely defeats vacuumdb's -j option. Hence, invent options SKIP_DATABASE_STATS and ONLY_DATABASE_STATS to allow applications to postpone vac_update_datfrozenxid() until the end of a series of VACUUM requests, and teach vacuumdb to use them. Per bug #17717 from Gunnar L. Sadly, this answer doesn't seem like something we'd consider back-patching, so the performance problem will remain in v12-v15. Tom Lane and Nathan Bossart Discussion: https://postgr.es/m/17717-6c50eb1c7d23a886@postgresql.org
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/commands/vacuum.c42
-rw-r--r--src/backend/postmaster/autovacuum.c9
2 files changed, 44 insertions, 7 deletions
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 158b1b497bf..c4ed7efce36 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -114,6 +114,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool full = false;
bool disable_page_skipping = false;
bool process_toast = true;
+ bool skip_database_stats = false;
+ bool only_database_stats = false;
ListCell *lc;
/* index_cleanup and truncate values unspecified for now */
@@ -200,6 +202,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
params.nworkers = nworkers;
}
}
+ else if (strcmp(opt->defname, "skip_database_stats") == 0)
+ skip_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "only_database_stats") == 0)
+ only_database_stats = defGetBoolean(opt);
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -216,7 +222,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(freeze ? VACOPT_FREEZE : 0) |
(full ? VACOPT_FULL : 0) |
(disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0) |
- (process_toast ? VACOPT_PROCESS_TOAST : 0);
+ (process_toast ? VACOPT_PROCESS_TOAST : 0) |
+ (skip_database_stats ? VACOPT_SKIP_DATABASE_STATS : 0) |
+ (only_database_stats ? VACOPT_ONLY_DATABASE_STATS : 0);
/* sanity checks on options */
Assert(params.options & (VACOPT_VACUUM | VACOPT_ANALYZE));
@@ -349,6 +357,24 @@ vacuum(List *relations, VacuumParams *params,
(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 explicitly */
+ if (params->options & ~(VACOPT_VACUUM |
+ VACOPT_VERBOSE |
+ 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.
*
@@ -376,7 +402,12 @@ vacuum(List *relations, VacuumParams *params,
* Build list of relation(s) to process, putting any new data in
* vac_context for safekeeping.
*/
- if (relations != NIL)
+ if (params->options & VACOPT_ONLY_DATABASE_STATS)
+ {
+ /* We don't process any tables in this case */
+ Assert(relations == NIL);
+ }
+ else if (relations != NIL)
{
List *newrels = NIL;
ListCell *lc;
@@ -528,11 +559,11 @@ vacuum(List *relations, VacuumParams *params,
StartTransactionCommand();
}
- if ((params->options & VACOPT_VACUUM) && !IsAutoVacuumWorkerProcess())
+ if ((params->options & VACOPT_VACUUM) &&
+ !(params->options & VACOPT_SKIP_DATABASE_STATS))
{
/*
* Update pg_database.datfrozenxid, and truncate pg_xact if possible.
- * (autovacuum.c does this for itself.)
*/
vac_update_datfrozenxid();
}
@@ -560,13 +591,14 @@ vacuum_is_permitted_for_relation(Oid relid, Form_pg_class reltuple,
Assert((options & (VACOPT_VACUUM | VACOPT_ANALYZE)) != 0);
- /*
+ /*----------
* A role has privileges to vacuum or analyze the relation if any of the
* following are true:
* - the role is a superuser
* - the role owns the relation
* - the role owns the current database and the relation is not shared
* - the role has been granted the MAINTAIN privilege on the relation
+ *----------
*/
if (object_ownercheck(RelationRelationId, relid, GetUserId()) ||
(object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()) && !reltuple->relisshared) ||
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e40bd39b3f3..f5ea381c53e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2854,8 +2854,13 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_relid = relid;
tab->at_sharedrel = classForm->relisshared;
- /* Note that this skips toast relations */
- tab->at_params.options = (dovacuum ? VACOPT_VACUUM : 0) |
+ /*
+ * Select VACUUM options. Note we don't say VACOPT_PROCESS_TOAST, so
+ * that vacuum() skips toast relations. Also note we tell vacuum() to
+ * skip vac_update_datfrozenxid(); we'll do that separately.
+ */
+ tab->at_params.options =
+ (dovacuum ? (VACOPT_VACUUM | VACOPT_SKIP_DATABASE_STATS) : 0) |
(doanalyze ? VACOPT_ANALYZE : 0) |
(!wraparound ? VACOPT_SKIP_LOCKED : 0);