Add TRUNCATE parameter to VACUUM.
authorFujii Masao <fujii@postgresql.org>
Tue, 7 May 2019 17:10:33 +0000 (02:10 +0900)
committerFujii Masao <fujii@postgresql.org>
Tue, 7 May 2019 17:10:33 +0000 (02:10 +0900)
This commit adds new parameter to VACUUM command, TRUNCATE,
which specifies that VACUUM should attempt to truncate off
any empty pages at the end of the table and allow the disk space
for the truncated pages to be returned to the operating system.

This parameter, if specified, overrides the vacuum_truncate
reloption. If neither the reloption nor the VACUUM option is
used, the default is true, as before.

Author: Fujii Masao
Reviewed-by: Julien Rouhaud, Masahiko Sawada
Discussion: https://postgr.es/m/CAD21AoD+qtrSDL=GSma4Wd3kLYLeRC0hPna-YAdkDeV4z156vg@mail.gmail.com

doc/src/sgml/ref/create_table.sgml
doc/src/sgml/ref/vacuum.sgml
src/backend/access/heap/vacuumlazy.c
src/backend/commands/vacuum.c
src/backend/postmaster/autovacuum.c
src/bin/psql/tab-complete.c
src/include/commands/vacuum.h
src/test/regress/expected/vacuum.out
src/test/regress/sql/vacuum.sql

index 786510f94caaf498222dd6500a68b6d8c8101ed3..19af4eed9c3b6a10a991fc0a6e03a6cbab40e1dd 100644 (file)
@@ -1418,7 +1418,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       Disabling index cleanup can speed up <command>VACUUM</command> very
       significantly, but may also lead to severely bloated indexes if table
       modifications are frequent.  The <literal>INDEX_CLEANUP</literal>
-      parameter to <xref linkend="sql-vacuum"/>, if specified, overrides
+      parameter of <xref linkend="sql-vacuum"/>, if specified, overrides
       the value of this option.
      </para>
     </listitem>
@@ -1438,7 +1438,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       autovacuum do the truncation and the disk space for
       the truncated pages is returned to the operating system.
       Note that the truncation requires <literal>ACCESS EXCLUSIVE</literal>
-      lock on the table.
+      lock on the table. The <literal>TRUNCATE</literal> parameter
+      of <xref linkend="sql-vacuum"/>, if specified, overrides the value
+      of this option.
      </para>
     </listitem>
    </varlistentry>
index c652f8b0bcd754866b5a2c0e1f2f3163fdb52d96..f9b0fb879456562263f229461c4f6f7a629d6486 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> ]
+    TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
 
 <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
 
@@ -204,6 +205,24 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>TRUNCATE</literal></term>
+    <listitem>
+     <para>
+      Specifies that <command>VACUUM</command> should attempt to
+      truncate off any empty pages at the end of the table and allow
+      the disk space for the truncated pages to be returned to
+      the operating system. This is normally the desired behavior
+      and is the default unless the <literal>vacuum_truncate</literal>
+      option has been set to false for the table to be vacuumed.
+      Setting this option to false may be useful to avoid
+      <literal>ACCESS EXCLUSIVE</literal> lock on the table that
+      the truncation requires. This option is ignored if the
+      <literal>FULL</literal> option is used.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><replaceable class="parameter">boolean</replaceable></term>
     <listitem>
index 0f70dc883d19acdb3112686eb1f76ef21ec9a33f..f1a79059cdb1aedc689ee52dad4c83940acc04b2 100644 (file)
@@ -163,7 +163,8 @@ static void lazy_cleanup_index(Relation indrel,
                                   LVRelStats *vacrelstats);
 static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
                                 int tupindex, LVRelStats *vacrelstats, Buffer *vmbuffer);
-static bool should_attempt_truncation(Relation rel, LVRelStats *vacrelstats);
+static bool should_attempt_truncation(VacuumParams *params,
+                                                LVRelStats *vacrelstats);
 static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
 static BlockNumber count_nondeletable_pages(Relation onerel,
                                                 LVRelStats *vacrelstats);
@@ -210,6 +211,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 
        Assert(params != NULL);
        Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
+       Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
 
        /* not every AM requires these to be valid, but heap does */
        Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
@@ -308,7 +310,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
        /*
         * Optionally truncate the relation.
         */
-       if (should_attempt_truncation(onerel, vacrelstats))
+       if (should_attempt_truncation(params, vacrelstats))
                lazy_truncate_heap(onerel, vacrelstats);
 
        /* Report that we are now doing final cleanup */
@@ -652,7 +654,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
 
                /* see note above about forcing scanning of last page */
 #define FORCE_CHECK_PAGE() \
-               (blkno == nblocks - 1 && should_attempt_truncation(onerel, vacrelstats))
+               (blkno == nblocks - 1 && should_attempt_truncation(params, vacrelstats))
 
                pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
 
@@ -1845,12 +1847,11 @@ lazy_cleanup_index(Relation indrel,
  * careful to depend only on fields that lazy_scan_heap updates on-the-fly.
  */
 static bool
-should_attempt_truncation(Relation rel, LVRelStats *vacrelstats)
+should_attempt_truncation(VacuumParams *params, LVRelStats *vacrelstats)
 {
        BlockNumber possibly_freeable;
 
-       if (rel->rd_options != NULL &&
-               ((StdRdOptions *) rel->rd_options)->vacuum_truncate == false)
+       if (params->truncate == VACOPT_TERNARY_DISABLED)
                return false;
 
        possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
index 94fb6f2606397ed8fb24f6acf79746923f64a65d..afdd3307acd4c372921a9b3451d78486f506705b 100644 (file)
@@ -98,6 +98,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
 
        /* Set default value */
        params.index_cleanup = VACOPT_TERNARY_DEFAULT;
+       params.truncate = VACOPT_TERNARY_DEFAULT;
 
        /* Parse options list */
        foreach(lc, vacstmt->options)
@@ -126,6 +127,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, "truncate") == 0)
+                       params.truncate = get_vacopt_ternary_value(opt);
                else
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
@@ -1760,6 +1763,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
                        params->index_cleanup = VACOPT_TERNARY_DISABLED;
        }
 
+       /* Set truncate option based on reloptions if not yet */
+       if (params->truncate == VACOPT_TERNARY_DEFAULT)
+       {
+               if (onerel->rd_options == NULL ||
+                       ((StdRdOptions *) onerel->rd_options)->vacuum_truncate)
+                       params->truncate = VACOPT_TERNARY_ENABLED;
+               else
+                       params->truncate = VACOPT_TERNARY_DISABLED;
+       }
+
        /*
         * Remember the relation's TOAST relation for later, if the caller asked
         * us to process it.  In VACUUM FULL, though, the toast table is
index 53c91d92778e661a5ff5cd0449e1031021c38e27..acd8a9280b9a4ceea266a4845f21661dca50eb23 100644 (file)
@@ -2887,6 +2887,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
                        (doanalyze ? VACOPT_ANALYZE : 0) |
                        (!wraparound ? VACOPT_SKIP_LOCKED : 0);
                tab->at_params.index_cleanup = VACOPT_TERNARY_DEFAULT;
+               tab->at_params.truncate = VACOPT_TERNARY_DEFAULT;
                tab->at_params.freeze_min_age = freeze_min_age;
                tab->at_params.freeze_table_age = freeze_table_age;
                tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age;
index bcddc7601e4a54ff76b688dc5b97619325a12411..e4c03de221fc8691db34ec3c341dd3dfdd8ba8f2 100644 (file)
@@ -3466,8 +3466,8 @@ 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");
-               else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP"))
+                                                 "INDEX_CLEANUP", "TRUNCATE");
+               else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|INDEX_CLEANUP|TRUNCATE"))
                        COMPLETE_WITH("ON", "OFF");
        }
        else if (HeadMatches("VACUUM") && TailMatches("("))
index 9cc6e0d02352b0deb991b99c2f0fff463d7ea5a8..270f61b0832e35a15506c11d9ec088a7c758f1ef 100644 (file)
@@ -182,6 +182,8 @@ typedef struct VacuumParams
                                                                         * 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 */
 } VacuumParams;
 
 /* GUC parameters */
index 6ba7cd726bc9354778b4de3ae9a5365deafbee44..e6657a675e1dabb4d8ee1c6414f8b1df14ab76fb 100644 (file)
@@ -88,6 +88,28 @@ VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster;
 -- index cleanup option is ignored if VACUUM FULL
 VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
 VACUUM (FULL TRUE) no_index_cleanup;
+-- TRUNCATE option
+CREATE TABLE vac_truncate_test(i INT NOT NULL, j text)
+       WITH (vacuum_truncate=true, autovacuum_enabled=false);
+INSERT INTO vac_truncate_test VALUES (1, NULL), (NULL, NULL);
+ERROR:  null value in column "i" violates not-null constraint
+DETAIL:  Failing row contains (null, null).
+VACUUM (TRUNCATE FALSE) vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') > 0;
+ ?column? 
+----------
+ t
+(1 row)
+
+VACUUM vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') = 0;
+ ?column? 
+----------
+ t
+(1 row)
+
+VACUUM (TRUNCATE FALSE, FULL TRUE) vac_truncate_test;
+DROP TABLE vac_truncate_test;
 -- partitioned table
 CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
 CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
index 57e0f354ddaf73f4720cb60ce7c6e5a9a35d90d3..4fa90940dc8501fd4d77ef202484f1fb28fa91a7 100644 (file)
@@ -71,6 +71,17 @@ VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster;
 VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
 VACUUM (FULL TRUE) no_index_cleanup;
 
+-- TRUNCATE option
+CREATE TABLE vac_truncate_test(i INT NOT NULL, j text)
+       WITH (vacuum_truncate=true, autovacuum_enabled=false);
+INSERT INTO vac_truncate_test VALUES (1, NULL), (NULL, NULL);
+VACUUM (TRUNCATE FALSE) vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') > 0;
+VACUUM vac_truncate_test;
+SELECT pg_relation_size('vac_truncate_test') = 0;
+VACUUM (TRUNCATE FALSE, FULL TRUE) vac_truncate_test;
+DROP TABLE vac_truncate_test;
+
 -- partitioned table
 CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
 CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);