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