diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index c6d0a35e506..ab33b7fb0ff 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1473,20 +1473,27 @@ WITH ( MODULUS numeric_literal, REM - vacuum_index_cleanup, toast.vacuum_index_cleanup (boolean) + vacuum_index_cleanup, toast.vacuum_index_cleanup (enum) vacuum_index_cleanup storage parameter - Enables or disables index cleanup when VACUUM is - run on this table. The default value is true. - Disabling index cleanup can speed up VACUUM very - significantly, but may also lead to severely bloated indexes if table - modifications are frequent. The INDEX_CLEANUP - parameter of VACUUM, if specified, overrides - the value of this option. + Forces or disables index cleanup when VACUUM + is run on this table. The default value is + AUTO. With OFF, index + cleanup is disabled, with ON it is enabled, + and with AUTO a decision is made dynamically, + each time VACUUM runs. The dynamic behavior + allows VACUUM to avoid needlessly scanning + indexes to remove very few dead tuples. Forcibly disabling all + index cleanup can speed up VACUUM very + significantly, but may also lead to severely bloated indexes if + table modifications are frequent. The + INDEX_CLEANUP parameter of VACUUM, if + specified, overrides the value of this option. diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index 5f67c9d18b7..3df32b58ee6 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -32,7 +32,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ boolean ] DISABLE_PAGE_SKIPPING [ boolean ] SKIP_LOCKED [ boolean ] - INDEX_CLEANUP [ boolean ] + INDEX_CLEANUP { AUTO | ON | OFF } PROCESS_TOAST [ boolean ] TRUNCATE [ boolean ] PARALLEL integer @@ -193,20 +193,45 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ ). However, if index - cleanup is not performed regularly, performance may suffer, because - as the table is modified, indexes will accumulate dead tuples - and the table itself will accumulate dead line pointers that cannot be - removed until index cleanup is completed. This option has no effect - for tables that do not have an index and is ignored if the - FULL option is used. + Normally, VACUUM will skip index vacuuming + when there are very few dead tuples in the table. The cost of + processing all of the table's indexes is expected to greatly + exceed the benefit of removing dead index tuples when this + happens. This option can be used to force + VACUUM to process indexes when there are more + than zero dead tuples. The default is AUTO, + which allows VACUUM to skip index vacuuming + when appropriate. If INDEX_CLEANUP is set to + ON, VACUUM will + conservatively remove all dead tuples from indexes. This may be + useful for backwards compatibility with earlier releases of + PostgreSQL where this was the + standard behavior. + + + INDEX_CLEANUP can also be set to + OFF to force VACUUM to + always skip index vacuuming, even when + there are many dead tuples in the table. This may be useful + when it is necessary to make VACUUM run as + quickly as possible to avoid imminent transaction ID wraparound + (see ). However, the + wraparound failsafe mechanism controlled by will generally trigger + automatically to avoid transaction ID wraparound failure, and + should be preferred. If index cleanup is not performed + regularly, performance may suffer, because as the table is + modified indexes will accumulate dead tuples and the table + itself will accumulate dead line pointers that cannot be removed + until index cleanup is completed. + + + This option has no effect for tables that have no index and is + ignored if the FULL option is used. It also + has no effect on the transaction ID wraparound failsafe + mechanism. When triggered it will skip index vacuuming, even + when INDEX_CLEANUP is set to + ON. @@ -217,7 +242,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ index_cleanup != VACOPT_TERNARY_DEFAULT); - Assert(params->truncate != VACOPT_TERNARY_DEFAULT); - /* measure elapsed time iff autovacuum logging requires it */ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0) { @@ -557,14 +557,41 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, vacrel->rel = rel; vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes, &vacrel->indrels); + vacrel->failsafe_active = false; + vacrel->consider_bypass_optimization = true; + + /* + * The index_cleanup param either disables index vacuuming and cleanup or + * forces it to go ahead when we would otherwise apply the index bypass + * optimization. The default is 'auto', which leaves the final decision + * up to lazy_vacuum(). + * + * The truncate param allows user to avoid attempting relation truncation, + * though it can't force truncation to happen. + */ + Assert(params->index_cleanup != VACOPTVALUE_UNSPECIFIED); + Assert(params->truncate != VACOPTVALUE_UNSPECIFIED && + params->truncate != VACOPTVALUE_AUTO); vacrel->do_index_vacuuming = true; vacrel->do_index_cleanup = true; - vacrel->do_failsafe = false; - if (params->index_cleanup == VACOPT_TERNARY_DISABLED) + vacrel->do_rel_truncate = (params->truncate != VACOPTVALUE_DISABLED); + if (params->index_cleanup == VACOPTVALUE_DISABLED) { + /* Force disable index vacuuming up-front */ vacrel->do_index_vacuuming = false; vacrel->do_index_cleanup = false; } + else if (params->index_cleanup == VACOPTVALUE_ENABLED) + { + /* Force index vacuuming. Note that failsafe can still bypass. */ + vacrel->consider_bypass_optimization = false; + } + else + { + /* Default/auto, make all decisions dynamically */ + Assert(params->index_cleanup == VACOPTVALUE_AUTO); + } + vacrel->bstrategy = bstrategy; vacrel->old_rel_pages = rel->rd_rel->relpages; vacrel->old_live_tuples = rel->rd_rel->reltuples; @@ -632,7 +659,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, /* * Optionally truncate the relation. */ - if (should_attempt_truncation(vacrel, params)) + if (should_attempt_truncation(vacrel)) { /* * Update error traceback information. This is the last phase during @@ -791,7 +818,7 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, { msgfmt = _(" %u pages from table (%.2f%% of total) have %lld dead item identifiers\n"); - if (!vacrel->do_failsafe) + if (!vacrel->failsafe_active) appendStringInfoString(&buf, _("index scan bypassed:")); else appendStringInfoString(&buf, _("index scan bypassed by failsafe:")); @@ -893,8 +920,7 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) next_fsm_block_to_vacuum; PGRUsage ru0; Buffer vmbuffer = InvalidBuffer; - bool skipping_blocks, - have_vacuumed_indexes = false; + bool skipping_blocks; StringInfoData buf; const int initprog_index[] = { PROGRESS_VACUUM_PHASE, @@ -1048,7 +1074,7 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) * scanning of last page. */ #define FORCE_CHECK_PAGE() \ - (blkno == nblocks - 1 && should_attempt_truncation(vacrel, params)) + (blkno == nblocks - 1 && should_attempt_truncation(vacrel)) pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno); @@ -1166,8 +1192,8 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) } /* Remove the collected garbage tuples from table and indexes */ - lazy_vacuum(vacrel, false); - have_vacuumed_indexes = true; + vacrel->consider_bypass_optimization = false; + lazy_vacuum(vacrel); /* * Vacuum the Free Space Map to make newly-freed space visible on @@ -1579,7 +1605,7 @@ lazy_scan_heap(LVRelState *vacrel, VacuumParams *params, bool aggressive) /* If any tuples need to be deleted, perform final vacuum cycle */ if (dead_tuples->num_tuples > 0) - lazy_vacuum(vacrel, !have_vacuumed_indexes); + lazy_vacuum(vacrel); /* * Vacuum the remainder of the Free Space Map. We must do this whether or @@ -2064,9 +2090,9 @@ retry: * wraparound. */ static void -lazy_vacuum(LVRelState *vacrel, bool onecall) +lazy_vacuum(LVRelState *vacrel) { - bool do_bypass_optimization; + bool bypass; /* Should not end up here with no indexes */ Assert(vacrel->nindexes > 0); @@ -2099,8 +2125,8 @@ lazy_vacuum(LVRelState *vacrel, bool onecall) * It's far easier to ensure that 99%+ of all UPDATEs against a table use * HOT through careful tuning. */ - do_bypass_optimization = false; - if (onecall && vacrel->rel_pages > 0) + bypass = false; + if (vacrel->consider_bypass_optimization && vacrel->rel_pages > 0) { BlockNumber threshold; @@ -2132,12 +2158,11 @@ lazy_vacuum(LVRelState *vacrel, bool onecall) * expanded to cover more cases then this may need to be reconsidered. */ threshold = (double) vacrel->rel_pages * BYPASS_THRESHOLD_PAGES; - do_bypass_optimization = - (vacrel->lpdead_item_pages < threshold && - vacrel->lpdead_items < MAXDEADTUPLES(32L * 1024L * 1024L)); + bypass = (vacrel->lpdead_item_pages < threshold && + vacrel->lpdead_items < MAXDEADTUPLES(32L * 1024L * 1024L)); } - if (do_bypass_optimization) + if (bypass) { /* * There are almost zero TIDs. Behave as if there were precisely @@ -2177,7 +2202,7 @@ lazy_vacuum(LVRelState *vacrel, bool onecall) * vacuuming or heap vacuuming. This VACUUM operation won't end up * back here again. */ - Assert(vacrel->do_failsafe); + Assert(vacrel->failsafe_active); } /* @@ -2259,7 +2284,7 @@ lazy_vacuum_all_indexes(LVRelState *vacrel) */ Assert(vacrel->num_index_scans > 0 || vacrel->dead_tuples->num_tuples == vacrel->lpdead_items); - Assert(allindexes || vacrel->do_failsafe); + Assert(allindexes || vacrel->failsafe_active); /* * Increase and report the number of index scans. @@ -2580,7 +2605,7 @@ static bool lazy_check_wraparound_failsafe(LVRelState *vacrel) { /* Don't warn more than once per VACUUM */ - if (vacrel->do_failsafe) + if (vacrel->failsafe_active) return true; if (unlikely(vacuum_xid_failsafe_check(vacrel->relfrozenxid, @@ -2589,9 +2614,12 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel) Assert(vacrel->do_index_vacuuming); Assert(vacrel->do_index_cleanup); + vacrel->failsafe_active = true; + + /* Disable index vacuuming, index cleanup, and heap rel truncation */ vacrel->do_index_vacuuming = false; vacrel->do_index_cleanup = false; - vacrel->do_failsafe = true; + vacrel->do_rel_truncate = false; ereport(WARNING, (errmsg("bypassing nonessential maintenance of table \"%s.%s.%s\" as a failsafe after %d index scans", @@ -3136,14 +3164,11 @@ lazy_cleanup_one_index(Relation indrel, IndexBulkDeleteResult *istat, * careful to depend only on fields that lazy_scan_heap updates on-the-fly. */ static bool -should_attempt_truncation(LVRelState *vacrel, VacuumParams *params) +should_attempt_truncation(LVRelState *vacrel) { BlockNumber possibly_freeable; - if (params->truncate == VACOPT_TERNARY_DISABLED) - return false; - - if (vacrel->do_failsafe) + if (!vacrel->do_rel_truncate || vacrel->failsafe_active) return false; possibly_freeable = vacrel->rel_pages - vacrel->nonempty_pages; @@ -3207,7 +3232,6 @@ lazy_truncate_heap(LVRelState *vacrel) * We failed to establish the lock in the specified number of * retries. This means we give up truncating. */ - lock_waiter_detected = true; ereport(elevel, (errmsg("\"%s\": stopping truncate due to conflicting lock request", vacrel->relname))); @@ -3399,9 +3423,8 @@ count_nondeletable_pages(LVRelState *vacrel, bool *lock_waiter_detected) /* * Note: any non-unused item should be taken as a reason to keep - * this page. We formerly thought that DEAD tuples could be - * thrown away, but that's not so, because we'd not have cleaned - * out their index entries. + * this page. Even an LP_DEAD item makes truncation unsafe, since + * we must not have cleaned out its index entries. */ if (ItemIdIsUsed(itemid)) { diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 7421d7cfbd3..5c4bc15b441 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -88,7 +88,7 @@ static void vac_truncate_clog(TransactionId frozenXID, MultiXactId lastSaneMinMulti); static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params); static double compute_parallel_delay(void); -static VacOptTernaryValue get_vacopt_ternary_value(DefElem *def); +static VacOptValue get_vacoptval_from_boolean(DefElem *def); /* * Primary entry point for manual VACUUM and ANALYZE commands @@ -109,9 +109,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) bool process_toast = true; ListCell *lc; - /* Set default value */ - params.index_cleanup = VACOPT_TERNARY_DEFAULT; - params.truncate = VACOPT_TERNARY_DEFAULT; + /* index_cleanup and truncate values unspecified for now */ + params.index_cleanup = VACOPTVALUE_UNSPECIFIED; + params.truncate = VACOPTVALUE_UNSPECIFIED; /* By default parallel vacuum is enabled */ params.nworkers = 0; @@ -142,11 +142,25 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) else if (strcmp(opt->defname, "disable_page_skipping") == 0) disable_page_skipping = defGetBoolean(opt); else if (strcmp(opt->defname, "index_cleanup") == 0) - params.index_cleanup = get_vacopt_ternary_value(opt); + { + /* Interpret no string as the default, which is 'auto' */ + if (!opt->arg) + params.index_cleanup = VACOPTVALUE_AUTO; + else + { + char *sval = defGetString(opt); + + /* Try matching on 'auto' string, or fall back on boolean */ + if (pg_strcasecmp(sval, "auto") == 0) + params.index_cleanup = VACOPTVALUE_AUTO; + else + params.index_cleanup = get_vacoptval_from_boolean(opt); + } + } else if (strcmp(opt->defname, "process_toast") == 0) process_toast = defGetBoolean(opt); else if (strcmp(opt->defname, "truncate") == 0) - params.truncate = get_vacopt_ternary_value(opt); + params.truncate = get_vacoptval_from_boolean(opt); else if (strcmp(opt->defname, "parallel") == 0) { if (opt->arg == NULL) @@ -1938,24 +1952,43 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) lockrelid = rel->rd_lockInfo.lockRelId; LockRelationIdForSession(&lockrelid, lmode); - /* Set index cleanup option based on reloptions if not yet */ - if (params->index_cleanup == VACOPT_TERNARY_DEFAULT) + /* + * Set index_cleanup option based on index_cleanup reloption if it wasn't + * specified in VACUUM command, or when running in an autovacuum worker + */ + if (params->index_cleanup == VACOPTVALUE_UNSPECIFIED) { - if (rel->rd_options == NULL || - ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup) - params->index_cleanup = VACOPT_TERNARY_ENABLED; + StdRdOptIndexCleanup vacuum_index_cleanup; + + if (rel->rd_options == NULL) + vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO; else - params->index_cleanup = VACOPT_TERNARY_DISABLED; + vacuum_index_cleanup = + ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup; + + if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO) + params->index_cleanup = VACOPTVALUE_AUTO; + else if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON) + params->index_cleanup = VACOPTVALUE_ENABLED; + else + { + Assert(vacuum_index_cleanup == + STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF); + params->index_cleanup = VACOPTVALUE_DISABLED; + } } - /* Set truncate option based on reloptions if not yet */ - if (params->truncate == VACOPT_TERNARY_DEFAULT) + /* + * Set truncate option based on truncate reloption if it wasn't specified + * in VACUUM command, or when running in an autovacuum worker + */ + if (params->truncate == VACOPTVALUE_UNSPECIFIED) { if (rel->rd_options == NULL || ((StdRdOptions *) rel->rd_options)->vacuum_truncate) - params->truncate = VACOPT_TERNARY_ENABLED; + params->truncate = VACOPTVALUE_ENABLED; else - params->truncate = VACOPT_TERNARY_DISABLED; + params->truncate = VACOPTVALUE_DISABLED; } /* @@ -2217,11 +2250,11 @@ compute_parallel_delay(void) /* * A wrapper function of defGetBoolean(). * - * This function returns VACOPT_TERNARY_ENABLED and VACOPT_TERNARY_DISABLED - * instead of true and false. + * This function returns VACOPTVALUE_ENABLED and VACOPTVALUE_DISABLED instead + * of true and false. */ -static VacOptTernaryValue -get_vacopt_ternary_value(DefElem *def) +static VacOptValue +get_vacoptval_from_boolean(DefElem *def) { - return defGetBoolean(def) ? VACOPT_TERNARY_ENABLED : VACOPT_TERNARY_DISABLED; + return defGetBoolean(def) ? VACOPTVALUE_ENABLED : VACOPTVALUE_DISABLED; } diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index d516df0ac5c..912ef9cb54c 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2976,8 +2976,14 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, tab->at_params.options = (dovacuum ? VACOPT_VACUUM : 0) | (doanalyze ? VACOPT_ANALYZE : 0) | (!wraparound ? VACOPT_SKIP_LOCKED : 0); - tab->at_params.index_cleanup = VACOPT_TERNARY_DEFAULT; - tab->at_params.truncate = VACOPT_TERNARY_DEFAULT; + + /* + * index_cleanup and truncate are unspecified at first in autovacuum. + * They will be filled in with usable values using their reloptions + * (or reloption defaults) later. + */ + tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED; + tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED; /* As of now, we don't support parallel vacuum for autovacuum */ tab->at_params.nworkers = -1; tab->at_params.freeze_min_age = freeze_min_age; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index bd8e9ea2f8a..38af5682f2d 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3917,8 +3917,10 @@ psql_completion(const char *text, int start, int end) "DISABLE_PAGE_SKIPPING", "SKIP_LOCKED", "INDEX_CLEANUP", "PROCESS_TOAST", "TRUNCATE", "PARALLEL"); - else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|PROCESS_TOAST|TRUNCATE")) + else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_TOAST|TRUNCATE")) COMPLETE_WITH("ON", "OFF"); + else if (TailMatches("INDEX_CLEANUP")) + COMPLETE_WITH("AUTO", "ON", "OFF"); } else if (HeadMatches("VACUUM") && TailMatches("(")) /* "VACUUM (" should be caught above, so assume we want columns */ diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index 069a861aab7..122e8932f1b 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -39,7 +39,8 @@ typedef struct vacuumingOptions int min_mxid_age; int parallel_workers; /* >= 0 indicates user specified the * parallel degree, otherwise -1 */ - bool do_index_cleanup; + bool no_index_cleanup; + bool force_index_cleanup; bool do_truncate; bool process_toast; } vacuumingOptions; @@ -99,8 +100,9 @@ main(int argc, char *argv[]) {"min-xid-age", required_argument, NULL, 6}, {"min-mxid-age", required_argument, NULL, 7}, {"no-index-cleanup", no_argument, NULL, 8}, - {"no-truncate", no_argument, NULL, 9}, - {"no-process-toast", no_argument, NULL, 10}, + {"force-index-cleanup", no_argument, NULL, 9}, + {"no-truncate", no_argument, NULL, 10}, + {"no-process-toast", no_argument, NULL, 11}, {NULL, 0, NULL, 0} }; @@ -126,7 +128,8 @@ main(int argc, char *argv[]) /* initialize options */ memset(&vacopts, 0, sizeof(vacopts)); vacopts.parallel_workers = -1; - vacopts.do_index_cleanup = true; + vacopts.no_index_cleanup = false; + vacopts.force_index_cleanup = false; vacopts.do_truncate = true; vacopts.process_toast = true; @@ -233,12 +236,15 @@ main(int argc, char *argv[]) } break; case 8: - vacopts.do_index_cleanup = false; + vacopts.no_index_cleanup = true; break; case 9: - vacopts.do_truncate = false; + vacopts.force_index_cleanup = true; break; case 10: + vacopts.do_truncate = false; + break; + case 11: vacopts.process_toast = false; break; default: @@ -285,12 +291,18 @@ main(int argc, char *argv[]) "disable-page-skipping"); exit(1); } - if (!vacopts.do_index_cleanup) + if (vacopts.no_index_cleanup) { pg_log_error("cannot use the \"%s\" option when performing only analyze", "no-index-cleanup"); exit(1); } + if (vacopts.force_index_cleanup) + { + pg_log_error("cannot use the \"%s\" option when performing only analyze", + "force-index-cleanup"); + exit(1); + } if (!vacopts.do_truncate) { pg_log_error("cannot use the \"%s\" option when performing only analyze", @@ -323,6 +335,14 @@ main(int argc, char *argv[]) } } + /* Prohibit --no-index-cleanup and --force-index-cleanup together */ + if (vacopts.no_index_cleanup && vacopts.force_index_cleanup) + { + pg_log_error("cannot use the \"%s\" option with the \"%s\" option", + "no-index-cleanup", "force-index-cleanup"); + exit(1); + } + /* fill cparams except for dbname, which is set below */ cparams.pghost = host; cparams.pgport = port; @@ -453,7 +473,7 @@ vacuum_one_database(ConnParams *cparams, exit(1); } - if (!vacopts->do_index_cleanup && PQserverVersion(conn) < 120000) + if (vacopts->no_index_cleanup && PQserverVersion(conn) < 120000) { PQfinish(conn); pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s", @@ -461,6 +481,14 @@ vacuum_one_database(ConnParams *cparams, exit(1); } + if (vacopts->force_index_cleanup && PQserverVersion(conn) < 120000) + { + PQfinish(conn); + pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s", + "force-index-cleanup", "12"); + exit(1); + } + if (!vacopts->do_truncate && PQserverVersion(conn) < 120000) { PQfinish(conn); @@ -878,13 +906,29 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion, appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep); sep = comma; } - if (!vacopts->do_index_cleanup) + if (vacopts->no_index_cleanup) { - /* INDEX_CLEANUP is supported since v12 */ + /* "INDEX_CLEANUP FALSE" has been supported since v12 */ Assert(serverVersion >= 120000); + Assert(!vacopts->force_index_cleanup); appendPQExpBuffer(sql, "%sINDEX_CLEANUP FALSE", sep); sep = comma; } + if (vacopts->force_index_cleanup) + { + /* + * "INDEX_CLEANUP TRUE" has been supported since v12. + * + * Though --force-index-cleanup was added to vacuumdb in v14, + * the "INDEX_CLEANUP TRUE" server/VACUUM behavior has never + * changed. No reason not to support --force-index-cleanup on + * v12+. + */ + Assert(serverVersion >= 120000); + Assert(!vacopts->no_index_cleanup); + appendPQExpBuffer(sql, "%sINDEX_CLEANUP TRUE", sep); + sep = comma; + } if (!vacopts->do_truncate) { /* TRUNCATE is supported since v12 */ @@ -998,6 +1042,7 @@ help(const char *progname) printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n")); printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n")); printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n")); + printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n")); printf(_(" --no-process-toast skip the TOAST table associated with the table to vacuum\n")); printf(_(" --no-truncate don't truncate empty pages at the end of the table\n")); printf(_(" -P, --parallel=PARALLEL_WORKERS use this many background workers for vacuum, if available\n")); diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index cb27257bb65..bf3126aa9bb 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -185,17 +185,20 @@ typedef struct VacAttrStats #define VACOPT_DISABLE_PAGE_SKIPPING 0x80 /* don't skip any pages */ /* - * A ternary value used by vacuum parameters. + * Values used by index_cleanup and truncate params. * - * DEFAULT value is used to determine the value based on other - * configurations, e.g. reloptions. + * VACOPTVALUE_UNSPECIFIED is used as an initial placeholder when VACUUM + * command has no explicit value. When that happens the final usable value + * comes from the corresponding reloption (though the reloption default is + * usually used). */ -typedef enum VacOptTernaryValue +typedef enum VacOptValue { - VACOPT_TERNARY_DEFAULT = 0, - VACOPT_TERNARY_DISABLED, - VACOPT_TERNARY_ENABLED, -} VacOptTernaryValue; + VACOPTVALUE_UNSPECIFIED = 0, + VACOPTVALUE_AUTO, + VACOPTVALUE_DISABLED, + VACOPTVALUE_ENABLED, +} VacOptValue; /* * Parameters customizing behavior of VACUUM and ANALYZE. @@ -216,10 +219,8 @@ typedef struct VacuumParams int log_min_duration; /* minimum execution threshold in ms at * which verbose logs are activated, -1 * to use default */ - VacOptTernaryValue index_cleanup; /* Do index vacuum and cleanup, - * default value depends on reloptions */ - VacOptTernaryValue truncate; /* Truncate empty pages at the end, - * default value depends on reloptions */ + VacOptValue index_cleanup; /* Do index vacuum and cleanup */ + VacOptValue truncate; /* Truncate empty pages at the end */ /* * The number of parallel vacuum workers. 0 by default which means choose diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 774ac5b2b19..77d176a9348 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -307,6 +307,14 @@ typedef struct AutoVacOpts float8 analyze_scale_factor; } AutoVacOpts; +/* StdRdOptions->vacuum_index_cleanup values */ +typedef enum StdRdOptIndexCleanup +{ + STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0, + STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF, + STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON +} StdRdOptIndexCleanup; + typedef struct StdRdOptions { int32 vl_len_; /* varlena header (do not touch directly!) */ @@ -316,7 +324,7 @@ typedef struct StdRdOptions AutoVacOpts autovacuum; /* autovacuum-related options */ bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ - bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */ + StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ bool vacuum_truncate; /* enables vacuum to truncate a relation */ } StdRdOptions; diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index 5e657849aad..e5771462d57 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -146,13 +146,15 @@ VACUUM no_index_cleanup; -- Both parent relation and toast are cleaned up. ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true); VACUUM no_index_cleanup; +ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = auto); +VACUUM no_index_cleanup; -- Parameter is set for both the parent table and its toast relation. INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(31,60), repeat('1234567890',269)); DELETE FROM no_index_cleanup WHERE i < 45; -- Only toast index is cleaned up. -ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = false, - toast.vacuum_index_cleanup = true); +ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = off, + toast.vacuum_index_cleanup = yes); VACUUM no_index_cleanup; -- Only parent is cleaned up. ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true, @@ -160,7 +162,7 @@ ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true, VACUUM no_index_cleanup; -- Test some extra relations. VACUUM (INDEX_CLEANUP FALSE) vaccluster; -VACUUM (INDEX_CLEANUP FALSE) vactst; -- index cleanup option is ignored if no indexes +VACUUM (INDEX_CLEANUP AUTO) vactst; -- index cleanup option is ignored if no indexes VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster; -- TRUNCATE option CREATE TABLE vac_truncate_test(i INT NOT NULL, j text) diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index 93fd258fc02..f220fc28a70 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -127,13 +127,15 @@ VACUUM no_index_cleanup; -- Both parent relation and toast are cleaned up. ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true); VACUUM no_index_cleanup; +ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = auto); +VACUUM no_index_cleanup; -- Parameter is set for both the parent table and its toast relation. INSERT INTO no_index_cleanup(i, t) VALUES (generate_series(31,60), repeat('1234567890',269)); DELETE FROM no_index_cleanup WHERE i < 45; -- Only toast index is cleaned up. -ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = false, - toast.vacuum_index_cleanup = true); +ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = off, + toast.vacuum_index_cleanup = yes); VACUUM no_index_cleanup; -- Only parent is cleaned up. ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true, @@ -141,7 +143,7 @@ ALTER TABLE no_index_cleanup SET (vacuum_index_cleanup = true, VACUUM no_index_cleanup; -- Test some extra relations. VACUUM (INDEX_CLEANUP FALSE) vaccluster; -VACUUM (INDEX_CLEANUP FALSE) vactst; -- index cleanup option is ignored if no indexes +VACUUM (INDEX_CLEANUP AUTO) vactst; -- index cleanup option is ignored if no indexes VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster; -- TRUNCATE option