Commit b84dbc8e authored by Fujii Masao's avatar Fujii Masao

Add TRUNCATE parameter to VACUUM.

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
parent 98719af6
...@@ -1418,7 +1418,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM ...@@ -1418,7 +1418,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
Disabling index cleanup can speed up <command>VACUUM</command> very Disabling index cleanup can speed up <command>VACUUM</command> very
significantly, but may also lead to severely bloated indexes if table significantly, but may also lead to severely bloated indexes if table
modifications are frequent. The <literal>INDEX_CLEANUP</literal> 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. the value of this option.
</para> </para>
</listitem> </listitem>
...@@ -1438,7 +1438,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM ...@@ -1438,7 +1438,9 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
autovacuum do the truncation and the disk space for autovacuum do the truncation and the disk space for
the truncated pages is returned to the operating system. the truncated pages is returned to the operating system.
Note that the truncation requires <literal>ACCESS EXCLUSIVE</literal> 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -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> ]
TRUNCATE [ <replaceable class="parameter">boolean</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase> <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
...@@ -204,6 +205,24 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet ...@@ -204,6 +205,24 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term> <term><replaceable class="parameter">boolean</replaceable></term>
<listitem> <listitem>
......
...@@ -163,7 +163,8 @@ static void lazy_cleanup_index(Relation indrel, ...@@ -163,7 +163,8 @@ static void lazy_cleanup_index(Relation indrel,
LVRelStats *vacrelstats); LVRelStats *vacrelstats);
static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer,
int tupindex, LVRelStats *vacrelstats, Buffer *vmbuffer); 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 void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
static BlockNumber count_nondeletable_pages(Relation onerel, static BlockNumber count_nondeletable_pages(Relation onerel,
LVRelStats *vacrelstats); LVRelStats *vacrelstats);
...@@ -210,6 +211,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, ...@@ -210,6 +211,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
Assert(params != NULL); Assert(params != NULL);
Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT); Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT);
Assert(params->truncate != VACOPT_TERNARY_DEFAULT);
/* not every AM requires these to be valid, but heap does */ /* not every AM requires these to be valid, but heap does */
Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)); Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid));
...@@ -308,7 +310,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, ...@@ -308,7 +310,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
/* /*
* Optionally truncate the relation. * Optionally truncate the relation.
*/ */
if (should_attempt_truncation(onerel, vacrelstats)) if (should_attempt_truncation(params, vacrelstats))
lazy_truncate_heap(onerel, vacrelstats); lazy_truncate_heap(onerel, vacrelstats);
/* Report that we are now doing final cleanup */ /* Report that we are now doing final cleanup */
...@@ -652,7 +654,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats, ...@@ -652,7 +654,7 @@ lazy_scan_heap(Relation onerel, VacuumParams *params, LVRelStats *vacrelstats,
/* see note above about forcing scanning of last page */ /* see note above about forcing scanning of last page */
#define FORCE_CHECK_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); pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
...@@ -1845,12 +1847,11 @@ lazy_cleanup_index(Relation indrel, ...@@ -1845,12 +1847,11 @@ lazy_cleanup_index(Relation indrel,
* careful to depend only on fields that lazy_scan_heap updates on-the-fly. * careful to depend only on fields that lazy_scan_heap updates on-the-fly.
*/ */
static bool static bool
should_attempt_truncation(Relation rel, LVRelStats *vacrelstats) should_attempt_truncation(VacuumParams *params, LVRelStats *vacrelstats)
{ {
BlockNumber possibly_freeable; BlockNumber possibly_freeable;
if (rel->rd_options != NULL && if (params->truncate == VACOPT_TERNARY_DISABLED)
((StdRdOptions *) rel->rd_options)->vacuum_truncate == false)
return false; return false;
possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
......
...@@ -98,6 +98,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) ...@@ -98,6 +98,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* Set default value */ /* Set default value */
params.index_cleanup = VACOPT_TERNARY_DEFAULT; params.index_cleanup = VACOPT_TERNARY_DEFAULT;
params.truncate = VACOPT_TERNARY_DEFAULT;
/* Parse options list */ /* Parse options list */
foreach(lc, vacstmt->options) foreach(lc, vacstmt->options)
...@@ -126,6 +127,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) ...@@ -126,6 +127,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, "truncate") == 0)
params.truncate = get_vacopt_ternary_value(opt);
else else
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
...@@ -1760,6 +1763,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) ...@@ -1760,6 +1763,16 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
params->index_cleanup = VACOPT_TERNARY_DISABLED; 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 * Remember the relation's TOAST relation for later, if the caller asked
* us to process it. In VACUUM FULL, though, the toast table is * us to process it. In VACUUM FULL, though, the toast table is
......
...@@ -2887,6 +2887,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, ...@@ -2887,6 +2887,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
(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;
tab->at_params.truncate = VACOPT_TERNARY_DEFAULT;
tab->at_params.freeze_min_age = freeze_min_age; tab->at_params.freeze_min_age = freeze_min_age;
tab->at_params.freeze_table_age = freeze_table_age; tab->at_params.freeze_table_age = freeze_table_age;
tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age; tab->at_params.multixact_freeze_min_age = multixact_freeze_min_age;
......
...@@ -3466,8 +3466,8 @@ psql_completion(const char *text, int start, int end) ...@@ -3466,8 +3466,8 @@ 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"); "INDEX_CLEANUP", "TRUNCATE");
else if (TailMatches("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|TRUNCATE"))
COMPLETE_WITH("ON", "OFF"); COMPLETE_WITH("ON", "OFF");
} }
else if (HeadMatches("VACUUM") && TailMatches("(")) else if (HeadMatches("VACUUM") && TailMatches("("))
......
...@@ -182,6 +182,8 @@ typedef struct VacuumParams ...@@ -182,6 +182,8 @@ typedef struct VacuumParams
* to use default */ * to use default */
VacOptTernaryValue index_cleanup; /* Do index vacuum and cleanup, VacOptTernaryValue index_cleanup; /* Do index vacuum and cleanup,
* default value depends on reloptions */ * default value depends on reloptions */
VacOptTernaryValue truncate; /* Truncate empty pages at the end,
* default value depends on reloptions */
} VacuumParams; } VacuumParams;
/* GUC parameters */ /* GUC parameters */
......
...@@ -88,6 +88,28 @@ VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster; ...@@ -88,6 +88,28 @@ VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster;
-- index cleanup option is ignored if VACUUM FULL -- index cleanup option is ignored if VACUUM FULL
VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup; VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
VACUUM (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 -- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
......
...@@ -71,6 +71,17 @@ VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster; ...@@ -71,6 +71,17 @@ VACUUM (INDEX_CLEANUP FALSE, FREEZE TRUE) vaccluster;
VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup; VACUUM (INDEX_CLEANUP TRUE, FULL TRUE) no_index_cleanup;
VACUUM (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 -- partitioned table
CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a); CREATE TABLE vacparted (a int, b char) PARTITION BY LIST (a);
CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1); CREATE TABLE vacparted1 PARTITION OF vacparted FOR VALUES IN (1);
......
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