diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 22dbc07..b3b518f 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1488,6 +1488,24 @@ WITH ( MODULUS numeric_literal, REM + vacuum_shrink_enabled (boolean) + + + Enables or disables shrinking the table when it's vacuumed. + This also applies to autovacuum. + The default is true. If true, VACUUM frees empty pages at the end of the table. + Shrinking the table requires ACCESS EXCLUSIVE lock on the table. + It can take non-negligible time when the shared buffer is large. Besides, ACCESS EXCLUSIVE + lock could lead to query cancellation on the standby server. + If the workload is likely to reuse the freed space soon + (e.g., UPDATE-heavy, or new rows will be added after deleting + old rows), setting this parameter to false makes sense to avoid unnecessary shrinking. + This parameter cannot be set for TOAST tables. + + + + + user_catalog_table (boolean) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index cdf1f4a..1c4e8e2 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -89,6 +89,11 @@ * Setting parallel_workers is safe, since it acts the same as * max_parallel_workers_per_gather which is a USERSET parameter that doesn't * affect existing plans or queries. + * + * vacuum_shrink_enabled can be set at ShareUpdateExclusiveLock because it + * is only used during VACUUM, which uses a ShareUpdateExclusiveLock, + * so the VACUUM will not be affected by in-flight changes. Changing its + * value has no affect until the next VACUUM, so no need for stronger lock. */ static relopt_bool boolRelOpts[] = @@ -113,6 +118,15 @@ static relopt_bool boolRelOpts[] = }, { { + "vacuum_shrink_enabled", + "Enables shrinking this table at vacuum", + RELOPT_KIND_HEAP, + ShareUpdateExclusiveLock + }, + true + }, + { + { "user_catalog_table", "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", RELOPT_KIND_HEAP, @@ -1383,6 +1397,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)}, {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)}, + {"vacuum_shrink_enabled", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, vacuum_shrink_enabled)}, {"user_catalog_table", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, user_catalog_table)}, {"parallel_workers", RELOPT_TYPE_INT, diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 37aa484..942bb92 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -163,7 +163,7 @@ 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(LVRelStats *vacrelstats); +static bool should_attempt_truncation(Relation rel, LVRelStats *vacrelstats); static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats); static BlockNumber count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats); @@ -285,7 +285,7 @@ heap_vacuum_rel(Relation onerel, int options, VacuumParams *params, /* * Optionally truncate the relation. */ - if (should_attempt_truncation(vacrelstats)) + if (should_attempt_truncation(onerel, vacrelstats)) lazy_truncate_heap(onerel, vacrelstats); /* Report that we are now doing final cleanup */ @@ -630,7 +630,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, /* see note above about forcing scanning of last page */ #define FORCE_CHECK_PAGE() \ - (blkno == nblocks - 1 && should_attempt_truncation(vacrelstats)) + (blkno == nblocks - 1 && should_attempt_truncation(onerel, vacrelstats)) pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno); @@ -1790,10 +1790,14 @@ lazy_cleanup_index(Relation indrel, * careful to depend only on fields that lazy_scan_heap updates on-the-fly. */ static bool -should_attempt_truncation(LVRelStats *vacrelstats) +should_attempt_truncation(Relation rel, LVRelStats *vacrelstats) { BlockNumber possibly_freeable; + if (rel->rd_options != NULL && + ((StdRdOptions *) rel->rd_options)->vacuum_shrink_enabled == false) + return false; + possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; if (possibly_freeable > 0 && (possibly_freeable >= REL_TRUNCATE_MINIMUM || diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 7b7a88f..a9ed116 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1039,6 +1039,7 @@ static const char *const table_storage_parameters[] = { "toast.log_autovacuum_min_duration", "toast_tuple_target", "user_catalog_table", + "vacuum_shrink_enabled", NULL }; diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 1d05465..e0cbfb6 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -251,6 +251,7 @@ typedef struct StdRdOptions float8 vacuum_cleanup_index_scale_factor; int toast_tuple_target; /* target for tuple toasting */ AutoVacOpts autovacuum; /* autovacuum-related options */ + bool vacuum_shrink_enabled; /* enable table shrinking at VACUUM */ bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ } StdRdOptions; diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out index f90c267..85693b7 100644 --- a/src/test/regress/expected/reloptions.out +++ b/src/test/regress/expected/reloptions.out @@ -86,6 +86,43 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND -- RESET fails if a value is specified ALTER TABLE reloptions_test RESET (fillfactor=12); ERROR: RESET must not include values for parameters +-- Test vacuum_shrink_enabled option +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT NOT NULL) + WITH (vacuum_shrink_enabled=false, autovacuum_enabled=false); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +-------------------------------------------------------- + {vacuum_shrink_enabled=false,autovacuum_enabled=false} +(1 row) + +INSERT INTO reloptions_test VALUES (1), (NULL); +ERROR: null value in column "i" violates not-null constraint +DETAIL: Failing row contains (null). +VACUUM reloptions_test; +SELECT pg_relation_size('reloptions_test') > 0; + ?column? +---------- + t +(1 row) + +ALTER TABLE reloptions_test RESET (vacuum_shrink_enabled); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +---------------------------- + {autovacuum_enabled=false} +(1 row) + +INSERT INTO reloptions_test VALUES (1), (NULL); +ERROR: null value in column "i" violates not-null constraint +DETAIL: Failing row contains (null). +VACUUM reloptions_test; +SELECT pg_relation_size('reloptions_test') = 0; + ?column? +---------- + t +(1 row) + -- Test toast.* options DROP TABLE reloptions_test; CREATE TABLE reloptions_test (s VARCHAR) diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql index 44fcd8c..1d1d495 100644 --- a/src/test/regress/sql/reloptions.sql +++ b/src/test/regress/sql/reloptions.sql @@ -52,6 +52,22 @@ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND -- RESET fails if a value is specified ALTER TABLE reloptions_test RESET (fillfactor=12); +-- Test vacuum_shrink_enabled option +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test(i INT NOT NULL) + WITH (vacuum_shrink_enabled=false, autovacuum_enabled=false); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +INSERT INTO reloptions_test VALUES (1), (NULL); +VACUUM reloptions_test; +SELECT pg_relation_size('reloptions_test') > 0; + +ALTER TABLE reloptions_test RESET (vacuum_shrink_enabled); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +INSERT INTO reloptions_test VALUES (1), (NULL); +VACUUM reloptions_test; +SELECT pg_relation_size('reloptions_test') = 0; + -- Test toast.* options DROP TABLE reloptions_test;