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>
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>
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>
</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>
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);
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));
/*
* 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 */
/* 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);
* 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;
/* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT;
+ params.truncate = VACOPT_TERNARY_DEFAULT;
/* Parse options list */
foreach(lc, vacstmt->options)
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),
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
(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;
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("("))
* 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 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);
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);