diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2023-01-06 14:17:25 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2023-01-06 14:17:25 -0500 |
commit | a46a7011b27188af526047a111969f257aaf4db8 (patch) | |
tree | 816e22b0b77bcc10da44ed043eef2879615bc399 /src/backend | |
parent | cd4b2334db4980bbf86a8ba1d446db17e62ca342 (diff) | |
download | postgresql-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.c | 42 | ||||
-rw-r--r-- | src/backend/postmaster/autovacuum.c | 9 |
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); |