Add option PROCESS_TOAST to VACUUM
authorMichael Paquier <[email protected]>
Tue, 9 Feb 2021 05:13:57 +0000 (14:13 +0900)
committerMichael Paquier <[email protected]>
Tue, 9 Feb 2021 05:13:57 +0000 (14:13 +0900)
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

doc/src/sgml/ref/vacuum.sgml
doc/src/sgml/ref/vacuumdb.sgml
src/backend/commands/vacuum.c
src/backend/postmaster/autovacuum.c
src/bin/psql/tab-complete.c
src/bin/scripts/t/100_vacuumdb.pl
src/bin/scripts/vacuumdb.c
src/include/commands/vacuum.h
src/test/regress/expected/vacuum.out
src/test/regress/sql/vacuum.sql

index 21ab57d880489e536918821371c34dcc0b9d75c8..4bb624979bdf46aeef5119e2705ecc518716de7c 100644 (file)
@@ -33,6 +33,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     DISABLE_PAGE_SKIPPING [ <replaceable class="parameter">boolean</replaceable> ]
     SKIP_LOCKED [ <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> ]
     PARALLEL <replaceable class="parameter">integer</replaceable>
 
@@ -210,6 +211,20 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </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>
     <term><literal>TRUNCATE</literal></term>
     <listitem>
index efd1d6c1e64e317435f8a27687aa2cb4b36decfb..843a82e871172507f1ad8efdbc1d8805a4908210 100644 (file)
@@ -244,6 +244,21 @@ PostgreSQL documentation
       </listitem>
      </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>
       <term><option>--no-truncate</option></term>
       <listitem>
index 462f9a0f8225cfa7331b3e393abbc35333186c64..0f77c5aff499c0ce8831213d45dc38bbbf2de501 100644 (file)
@@ -104,6 +104,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
    bool        freeze = false;
    bool        full = false;
    bool        disable_page_skipping = false;
+   bool        process_toast = true;
    ListCell   *lc;
 
    /* Set default value */
@@ -140,6 +141,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
            disable_page_skipping = defGetBoolean(opt);
        else if (strcmp(opt->defname, "index_cleanup") == 0)
            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)
            params.truncate = get_vacopt_ternary_value(opt);
        else if (strcmp(opt->defname, "parallel") == 0)
@@ -189,13 +192,13 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
        (analyze ? VACOPT_ANALYZE : 0) |
        (freeze ? VACOPT_FREEZE : 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 */
    Assert(params.options & (VACOPT_VACUUM | VACOPT_ANALYZE));
    Assert((params.options & VACOPT_VACUUM) ||
           !(params.options & (VACOPT_FULL | VACOPT_FREEZE)));
-   Assert(!(params.options & VACOPT_SKIPTOAST));
 
    if ((params.options & VACOPT_FULL) && params.nworkers > 0)
        ereport(ERROR,
@@ -318,6 +321,13 @@ vacuum(List *relations, VacuumParams *params,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 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
     * in autovacuum --- autovacuum.c does this for itself.
@@ -1895,7 +1905,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
     * us to process it.  In VACUUM FULL, though, the toast table is
     * 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;
    else
        toast_relid = InvalidOid;
index 47e60ca5613f8668258aabdf35d3bb73d8ab463f..5360604933e230d2c972ed1ceeb7fd1b02ef045e 100644 (file)
@@ -2918,8 +2918,9 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
        tab = palloc(sizeof(autovac_table));
        tab->at_relid = relid;
        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) |
            (!wraparound ? VACOPT_SKIP_LOCKED : 0);
        tab->at_params.index_cleanup = VACOPT_TERNARY_DEFAULT;
index 5f0e775fd3f0945b36a7f70bcbe707734128f9af..1e1c315bae94aacd4c5f894c5841204b6a3a0f60 100644 (file)
@@ -3870,8 +3870,9 @@ psql_completion(const char *text, int start, int end)
        if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
            COMPLETE_WITH("FULL", "FREEZE", "ANALYZE", "VERBOSE",
                          "DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
-                         "INDEX_CLEANUP", "TRUNCATE", "PARALLEL");
-       else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
+                         "INDEX_CLEANUP", "PROCESS_TOAST",
+                         "TRUNCATE", "PARALLEL");
+       else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|PROCESS_TOAST|TRUNCATE"))
            COMPLETE_WITH("ON", "OFF");
    }
    else if (HeadMatches("VACUUM") && TailMatches("("))
index 9e36b6d2b05fe8ecb19185188ecf74a52226928a..99ec8bebde35099a181aad02e29843fafec9280d 100644 (file)
@@ -3,7 +3,7 @@ use warnings;
 
 use PostgresNode;
 use TestLib;
-use Test::More tests => 55;
+use Test::More tests => 58;
 
 program_help_ok('vacuumdb');
 program_version_ok('vacuumdb');
@@ -56,12 +56,19 @@ $node->command_fails(
    [ 'vacuumdb', '--analyze-only', '--no-index-cleanup', 'postgres' ],
    '--analyze-only and --no-index-cleanup specified together');
 $node->issues_sql_like(
-    [ 'vacuumdb', '--no-truncate', 'postgres' ],
-    qr/statement: VACUUM \(TRUNCATE FALSE\).*;/,
-    'vacuumdb --no-truncate');
+   [ 'vacuumdb', '--no-truncate', 'postgres' ],
+   qr/statement: VACUUM \(TRUNCATE FALSE\).*;/,
+   'vacuumdb --no-truncate');
 $node->command_fails(
-    [ 'vacuumdb', '--analyze-only', '--no-truncate', 'postgres' ],
-    '--analyze-only and --no-truncate specified together');
+   [ 'vacuumdb', '--analyze-only', '--no-truncate', 'postgres' ],
+   '--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(
    [ 'vacuumdb', '-P', 2, 'postgres' ],
    qr/statement: VACUUM \(PARALLEL 2\).*;/,
index 9dc8aca29f351fb08a2cec8dcfd8a35d8055bc0e..602fd45c4297967c54fcdf5dc13a7b514f6be019 100644 (file)
@@ -41,6 +41,7 @@ typedef struct vacuumingOptions
                                     * parallel degree, otherwise -1 */
    bool        do_index_cleanup;
    bool        do_truncate;
+   bool        process_toast;
 } vacuumingOptions;
 
 
@@ -99,6 +100,7 @@ main(int argc, char *argv[])
        {"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},
        {NULL, 0, NULL, 0}
    };
 
@@ -126,6 +128,7 @@ main(int argc, char *argv[])
    vacopts.parallel_workers = -1;
    vacopts.do_index_cleanup = true;
    vacopts.do_truncate = true;
+   vacopts.process_toast = true;
 
    pg_logging_init(argv[0]);
    progname = get_progname(argv[0]);
@@ -235,6 +238,9 @@ main(int argc, char *argv[])
            case 9:
                vacopts.do_truncate = false;
                break;
+           case 10:
+               vacopts.process_toast = false;
+               break;
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
                exit(1);
@@ -291,6 +297,12 @@ main(int argc, char *argv[])
                         "no-truncate");
            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' */
    }
 
@@ -456,6 +468,14 @@ vacuum_one_database(const ConnParams *cparams,
        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)
    {
        PQfinish(conn);
@@ -872,6 +892,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
                appendPQExpBuffer(sql, "%sTRUNCATE FALSE", sep);
                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)
            {
                /* SKIP_LOCKED is supported since v12 */
@@ -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-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-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_DEGREE  use this many background workers for vacuum, if available\n"));
    printf(_("  -q, --quiet                     don't write any messages\n"));
index 191cbbd0049390e43f7ae8ba1b6eacca3d44e4cb..d029da5ac0568a89e4210d480f77c86579595c96 100644 (file)
@@ -181,7 +181,7 @@ typedef struct VacAttrStats
 #define VACOPT_FREEZE 0x08     /* FREEZE option */
 #define VACOPT_FULL 0x10       /* FULL (non-concurrent) vacuum */
 #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 */
 
 /*
index 3fccb183c0fd4789a2eb616053b1b18b4146ae77..90cea6caa86d32d0fb19049a670d51605599bf6a 100644 (file)
@@ -252,6 +252,12 @@ RESET default_transaction_isolation;
 BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 ANALYZE vactst;
 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 vactst;
 DROP TABLE vacparted;
index c7b5f96f6bdfb518725aafe02bfc62088056885f..93fd258fc02cf77aa14adf9d7399406291ea418c 100644 (file)
@@ -213,6 +213,12 @@ BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 ANALYZE vactst;
 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 vactst;
 DROP TABLE vacparted;