Commit 7cb3048f authored by Michael Paquier's avatar Michael Paquier

Add option PROCESS_TOAST to VACUUM

This option controls if toast tables associated with a relation are
vacuumed or not when running a manual VACUUM.  It was already possible
to trigger a manual VACUUM on a toast relation without processing its
main relation, but a manual vacuum on a main relation always forced a
vacuum on its toast table.  This is useful in scenarios where the level
of bloat or transaction age of the main and toast relations differs a
lot.

This option is an extension of the existing VACOPT_SKIPTOAST that was
used by autovacuum to control if toast relations should be skipped or
not.  This internal flag is renamed to VACOPT_PROCESS_TOAST for
consistency with the new option.

A new option switch, called --no-process-toast, is added to vacuumdb.

Author: Nathan Bossart
Reviewed-by: Kirk Jamison, Michael Paquier, Justin Pryzby
Discussion: https://postgr.es/m/BA8951E9-1524-48C5-94AF-73B1F0D7857F@amazon.com
parent 5fd59002
...@@ -33,6 +33,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet ...@@ -33,6 +33,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
DISABLE_PAGE_SKIPPING [ <replaceable class="parameter">boolean</replaceable> ] DISABLE_PAGE_SKIPPING [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ] SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ] INDEX_CLEANUP [ <replaceable class="parameter">boolean</replaceable> ]
PROCESS_TOAST [ <replaceable class="parameter">boolean</replaceable> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ] TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
PARALLEL <replaceable class="parameter">integer</replaceable> PARALLEL <replaceable class="parameter">integer</replaceable>
...@@ -210,6 +211,20 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet ...@@ -210,6 +211,20 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><literal>PROCESS_TOAST</literal></term>
<listitem>
<para>
Specifies that <command>VACUUM</command> should attempt to process the
corresponding <literal>TOAST</literal> table for each relation, if one
exists. This is normally the desired behavior and is the default.
Setting this option to false may be useful when it is only necessary to
vacuum the main relation. This option is required when the
<literal>FULL</literal> option is used.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><literal>TRUNCATE</literal></term> <term><literal>TRUNCATE</literal></term>
<listitem> <listitem>
......
...@@ -244,6 +244,21 @@ PostgreSQL documentation ...@@ -244,6 +244,21 @@ PostgreSQL documentation
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term><option>--no-process-toast</option></term>
<listitem>
<para>
Skip the TOAST table associated with the table to vacuum, if any.
</para>
<note>
<para>
This option is only available for servers running
<productname>PostgreSQL</productname> 14 and later.
</para>
</note>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term><option>--no-truncate</option></term> <term><option>--no-truncate</option></term>
<listitem> <listitem>
......
...@@ -104,6 +104,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) ...@@ -104,6 +104,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool freeze = false; bool freeze = false;
bool full = false; bool full = false;
bool disable_page_skipping = false; bool disable_page_skipping = false;
bool process_toast = true;
ListCell *lc; ListCell *lc;
/* Set default value */ /* Set default value */
...@@ -140,6 +141,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) ...@@ -140,6 +141,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
disable_page_skipping = defGetBoolean(opt); disable_page_skipping = defGetBoolean(opt);
else if (strcmp(opt->defname, "index_cleanup") == 0) else if (strcmp(opt->defname, "index_cleanup") == 0)
params.index_cleanup = get_vacopt_ternary_value(opt); params.index_cleanup = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "process_toast") == 0)
process_toast = defGetBoolean(opt);
else if (strcmp(opt->defname, "truncate") == 0) else if (strcmp(opt->defname, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt); params.truncate = get_vacopt_ternary_value(opt);
else if (strcmp(opt->defname, "parallel") == 0) else if (strcmp(opt->defname, "parallel") == 0)
...@@ -189,13 +192,13 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) ...@@ -189,13 +192,13 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(analyze ? VACOPT_ANALYZE : 0) | (analyze ? VACOPT_ANALYZE : 0) |
(freeze ? VACOPT_FREEZE : 0) | (freeze ? VACOPT_FREEZE : 0) |
(full ? VACOPT_FULL : 0) | (full ? VACOPT_FULL : 0) |
(disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0); (disable_page_skipping ? VACOPT_DISABLE_PAGE_SKIPPING : 0) |
(process_toast ? VACOPT_PROCESS_TOAST : 0);
/* sanity checks on options */ /* sanity checks on options */
Assert(params.options & (VACOPT_VACUUM | VACOPT_ANALYZE)); Assert(params.options & (VACOPT_VACUUM | VACOPT_ANALYZE));
Assert((params.options & VACOPT_VACUUM) || Assert((params.options & VACOPT_VACUUM) ||
!(params.options & (VACOPT_FULL | VACOPT_FREEZE))); !(params.options & (VACOPT_FULL | VACOPT_FREEZE)));
Assert(!(params.options & VACOPT_SKIPTOAST));
if ((params.options & VACOPT_FULL) && params.nworkers > 0) if ((params.options & VACOPT_FULL) && params.nworkers > 0)
ereport(ERROR, ereport(ERROR,
...@@ -318,6 +321,13 @@ vacuum(List *relations, VacuumParams *params, ...@@ -318,6 +321,13 @@ vacuum(List *relations, VacuumParams *params,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL"))); 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")));
/* /*
* Send info about dead objects to the statistics collector, unless we are * Send info about dead objects to the statistics collector, unless we are
* in autovacuum --- autovacuum.c does this for itself. * in autovacuum --- autovacuum.c does this for itself.
...@@ -1895,7 +1905,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) ...@@ -1895,7 +1905,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
* us to process it. In VACUUM FULL, though, the toast table is * us to process it. In VACUUM FULL, though, the toast table is
* automatically rebuilt by cluster_rel so we shouldn't recurse to it. * automatically rebuilt by cluster_rel so we shouldn't recurse to it.
*/ */
if (!(params->options & VACOPT_SKIPTOAST) && !(params->options & VACOPT_FULL)) if ((params->options & VACOPT_PROCESS_TOAST) != 0 &&
(params->options & VACOPT_FULL) == 0)
toast_relid = onerel->rd_rel->reltoastrelid; toast_relid = onerel->rd_rel->reltoastrelid;
else else
toast_relid = InvalidOid; toast_relid = InvalidOid;
......
...@@ -2918,8 +2918,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, ...@@ -2918,8 +2918,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab = palloc(sizeof(autovac_table)); tab = palloc(sizeof(autovac_table));
tab->at_relid = relid; tab->at_relid = relid;
tab->at_sharedrel = classForm->relisshared; tab->at_sharedrel = classForm->relisshared;
tab->at_params.options = VACOPT_SKIPTOAST |
(dovacuum ? VACOPT_VACUUM : 0) | /* Note that this skips toast relations */
tab->at_params.options = (dovacuum ? VACOPT_VACUUM : 0) |
(doanalyze ? VACOPT_ANALYZE : 0) | (doanalyze ? VACOPT_ANALYZE : 0) |
(!wraparound ? VACOPT_SKIP_LOCKED : 0); (!wraparound ? VACOPT_SKIP_LOCKED : 0);
tab->at_params.index_cleanup = VACOPT_TERNARY_DEFAULT; tab->at_params.index_cleanup = VACOPT_TERNARY_DEFAULT;
......
...@@ -3870,8 +3870,9 @@ psql_completion(const char *text, int start, int end) ...@@ -3870,8 +3870,9 @@ psql_completion(const char *text, int start, int end)
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE", COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED", "DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "TRUNCATE", "PARALLEL"); "INDEX_CLEANUP", "PROCESS_TOAST",
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE")) "TRUNCATE", "PARALLEL");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|PROCESS_TOAST|TRUNCATE"))
COMPLETE_WITH("ON", "OFF"); COMPLETE_WITH("ON", "OFF");
} }
else if (HeadMatches("VACUUM") && TailMatches("(")) else if (HeadMatches("VACUUM") && TailMatches("("))
......
...@@ -3,7 +3,7 @@ use warnings; ...@@ -3,7 +3,7 @@ use warnings;
use PostgresNode; use PostgresNode;
use TestLib; use TestLib;
use Test::More tests => 55; use Test::More tests => 58;
program_help_ok('vacuumdb'); program_help_ok('vacuumdb');
program_version_ok('vacuumdb'); program_version_ok('vacuumdb');
...@@ -56,12 +56,19 @@ $node->command_fails( ...@@ -56,12 +56,19 @@ $node->command_fails(
[ 'vacuumdb', '--analyze-only', '--no-index-cleanup', 'postgres' ], [ 'vacuumdb', '--analyze-only', '--no-index-cleanup', 'postgres' ],
'--analyze-only and --no-index-cleanup specified together'); '--analyze-only and --no-index-cleanup specified together');
$node->issues_sql_like( $node->issues_sql_like(
[ 'vacuumdb', '--no-truncate', 'postgres' ], [ 'vacuumdb', '--no-truncate', 'postgres' ],
qr/statement: VACUUM \(TRUNCATE FALSE\).*;/, qr/statement: VACUUM \(TRUNCATE FALSE\).*;/,
'vacuumdb --no-truncate'); 'vacuumdb --no-truncate');
$node->command_fails( $node->command_fails(
[ 'vacuumdb', '--analyze-only', '--no-truncate', 'postgres' ], [ 'vacuumdb', '--analyze-only', '--no-truncate', 'postgres' ],
'--analyze-only and --no-truncate specified together'); '--analyze-only and --no-truncate specified together');
$node->issues_sql_like(
[ 'vacuumdb', '--no-process-toast', 'postgres' ],
qr/statement: VACUUM \(PROCESS_TOAST FALSE\).*;/,
'vacuumdb --no-process-toast');
$node->command_fails(
[ 'vacuumdb', '--analyze-only', '--no-process-toast', 'postgres' ],
'--analyze-only and --no-process-toast specified together');
$node->issues_sql_like( $node->issues_sql_like(
[ 'vacuumdb', '-P', 2, 'postgres' ], [ 'vacuumdb', '-P', 2, 'postgres' ],
qr/statement: VACUUM \(PARALLEL 2\).*;/, qr/statement: VACUUM \(PARALLEL 2\).*;/,
......
...@@ -41,6 +41,7 @@ typedef struct vacuumingOptions ...@@ -41,6 +41,7 @@ typedef struct vacuumingOptions
* parallel degree, otherwise -1 */ * parallel degree, otherwise -1 */
bool do_index_cleanup; bool do_index_cleanup;
bool do_truncate; bool do_truncate;
bool process_toast;
} vacuumingOptions; } vacuumingOptions;
...@@ -99,6 +100,7 @@ main(int argc, char *argv[]) ...@@ -99,6 +100,7 @@ main(int argc, char *argv[])
{"min-mxid-age", required_argument, NULL, 7}, {"min-mxid-age", required_argument, NULL, 7},
{"no-index-cleanup", no_argument, NULL, 8}, {"no-index-cleanup", no_argument, NULL, 8},
{"no-truncate", no_argument, NULL, 9}, {"no-truncate", no_argument, NULL, 9},
{"no-process-toast", no_argument, NULL, 10},
{NULL, 0, NULL, 0} {NULL, 0, NULL, 0}
}; };
...@@ -126,6 +128,7 @@ main(int argc, char *argv[]) ...@@ -126,6 +128,7 @@ main(int argc, char *argv[])
vacopts.parallel_workers = -1; vacopts.parallel_workers = -1;
vacopts.do_index_cleanup = true; vacopts.do_index_cleanup = true;
vacopts.do_truncate = true; vacopts.do_truncate = true;
vacopts.process_toast = true;
pg_logging_init(argv[0]); pg_logging_init(argv[0]);
progname = get_progname(argv[0]); progname = get_progname(argv[0]);
...@@ -235,6 +238,9 @@ main(int argc, char *argv[]) ...@@ -235,6 +238,9 @@ main(int argc, char *argv[])
case 9: case 9:
vacopts.do_truncate = false; vacopts.do_truncate = false;
break; break;
case 10:
vacopts.process_toast = false;
break;
default: default:
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1); exit(1);
...@@ -291,6 +297,12 @@ main(int argc, char *argv[]) ...@@ -291,6 +297,12 @@ main(int argc, char *argv[])
"no-truncate"); "no-truncate");
exit(1); exit(1);
} }
if (!vacopts.process_toast)
{
pg_log_error("cannot use the \"%s\" option when performing only analyze",
"no-process-toast");
exit(1);
}
/* allow 'and_analyze' with 'analyze_only' */ /* allow 'and_analyze' with 'analyze_only' */
} }
...@@ -456,6 +468,14 @@ vacuum_one_database(const ConnParams *cparams, ...@@ -456,6 +468,14 @@ vacuum_one_database(const ConnParams *cparams,
exit(1); exit(1);
} }
if (!vacopts->process_toast && PQserverVersion(conn) < 140000)
{
PQfinish(conn);
pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"no-process-toast", "14");
exit(1);
}
if (vacopts->skip_locked && PQserverVersion(conn) < 120000) if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
{ {
PQfinish(conn); PQfinish(conn);
...@@ -872,6 +892,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion, ...@@ -872,6 +892,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep); appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
sep = comma; sep = comma;
} }
if (!vacopts->process_toast)
{
/* PROCESS_TOAST is supported since v14 */
Assert(serverVersion >= 140000);
appendPQExpBuffer(sql, "%sPROCESS_TOAST FALSE", sep);
sep = comma;
}
if (vacopts->skip_locked) if (vacopts->skip_locked)
{ {
/* SKIP_LOCKED is supported since v12 */ /* SKIP_LOCKED is supported since v12 */
...@@ -971,6 +998,7 @@ help(const char *progname) ...@@ -971,6 +998,7 @@ help(const char *progname)
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n")); 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(_(" --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(_(" --no-index-cleanup don't 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(_(" --no-truncate don't truncate empty pages at the end of the table\n"));
printf(_(" -P, --parallel=PARALLEL_DEGREE use this many background workers for vacuum, if available\n")); printf(_(" -P, --parallel=PARALLEL_DEGREE use this many background workers for vacuum, if available\n"));
printf(_(" -q, --quiet don't write any messages\n")); printf(_(" -q, --quiet don't write any messages\n"));
......
...@@ -181,7 +181,7 @@ typedef struct VacAttrStats ...@@ -181,7 +181,7 @@ typedef struct VacAttrStats
#define VACOPT_FREEZE 0x08 /* FREEZE option */ #define VACOPT_FREEZE 0x08 /* FREEZE option */
#define VACOPT_FULL 0x10 /* FULL (non-concurrent) vacuum */ #define VACOPT_FULL 0x10 /* FULL (non-concurrent) vacuum */
#define VACOPT_SKIP_LOCKED 0x20 /* skip if cannot get lock */ #define VACOPT_SKIP_LOCKED 0x20 /* skip if cannot get lock */
#define VACOPT_SKIPTOAST 0x40 /* don't process the TOAST table, if any */ #define VACOPT_PROCESS_TOAST 0x40 /* process the TOAST table, if any */
#define VACOPT_DISABLE_PAGE_SKIPPING 0x80 /* don't skip any pages */ #define VACOPT_DISABLE_PAGE_SKIPPING 0x80 /* don't skip any pages */
/* /*
......
...@@ -252,6 +252,12 @@ RESET default_transaction_isolation; ...@@ -252,6 +252,12 @@ RESET default_transaction_isolation;
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
ANALYZE vactst; ANALYZE vactst;
COMMIT; COMMIT;
-- PROCESS_TOAST option
ALTER TABLE vactst ADD COLUMN t TEXT;
ALTER TABLE vactst ALTER COLUMN t SET STORAGE EXTERNAL;
VACUUM (PROCESS_TOAST FALSE) vactst;
VACUUM (PROCESS_TOAST FALSE, FULL) vactst;
ERROR: PROCESS_TOAST required with VACUUM FULL
DROP TABLE vaccluster; DROP TABLE vaccluster;
DROP TABLE vactst; DROP TABLE vactst;
DROP TABLE vacparted; DROP TABLE vacparted;
......
...@@ -213,6 +213,12 @@ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; ...@@ -213,6 +213,12 @@ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
ANALYZE vactst; ANALYZE vactst;
COMMIT; COMMIT;
-- PROCESS_TOAST option
ALTER TABLE vactst ADD COLUMN t TEXT;
ALTER TABLE vactst ALTER COLUMN t SET STORAGE EXTERNAL;
VACUUM (PROCESS_TOAST FALSE) vactst;
VACUUM (PROCESS_TOAST FALSE, FULL) vactst;
DROP TABLE vaccluster; DROP TABLE vaccluster;
DROP TABLE vactst; DROP TABLE vactst;
DROP TABLE vacparted; DROP TABLE vacparted;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment