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 <replaceable class="parameter">numeric_literal</replaceable>, REM
    </varlistentry>
 
    <varlistentry>
+    <term><literal>vacuum_shrink_enabled</literal> (<type>boolean</type>)</term>
+    <listitem>
+     <para>
+     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 <literal>ACCESS EXCLUSIVE</literal> lock on the table.
+     It can take non-negligible time when the shared buffer is large.  Besides, <literal>ACCESS EXCLUSIVE</literal>
+     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.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>user_catalog_table</literal> (<type>boolean</type>)</term>
     <listitem>
      <para>
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;
 
