*** a/doc/src/sgml/ref/create_table.sgml
--- b/doc/src/sgml/ref/create_table.sgml
***************
*** 1404,1409 **** WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
--- 1404,1423 ----
      </listitem>
     </varlistentry>
  
+    <varlistentry>
+     <term><literal>vacuum_truncate_enabled</literal>, <literal>toast.vacuum_truncate_enabled</literal> (<type>boolean</type>)</term>
+     <listitem>
+      <para>
+       Enables vacuum to try to truncate off any empty pages at the end
+       of this table. The default value is <literal>true</literal>.
+       If <literal>true</literal>, the disk space for the truncated pages
+       is returned to the operating system, but note that
+       the truncation requires <literal>ACCESS EXCLUSIVE</literal> lock
+       on the table.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
     <varlistentry>
      <term><literal>autovacuum_vacuum_threshold</literal>, <literal>toast.autovacuum_vacuum_threshold</literal> (<type>integer</type>)</term>
      <listitem>
*** a/src/backend/access/common/reloptions.c
--- b/src/backend/access/common/reloptions.c
***************
*** 89,94 ****
--- 89,99 ----
   * 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_truncate_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[] =
***************
*** 147,152 **** static relopt_bool boolRelOpts[] =
--- 152,166 ----
  		},
  		true
  	},
+ 	{
+ 		{
+ 			"vacuum_truncate_enabled",
+ 			"Enables vacuum to truncate empty pages at the end of this table",
+ 			RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ 			ShareUpdateExclusiveLock
+ 		},
+ 		true
+ 	},
  	/* list terminator */
  	{{NULL}}
  };
***************
*** 1399,1405 **** default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
  		{"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL,
  		offsetof(StdRdOptions, vacuum_cleanup_index_scale_factor)},
  		{"vacuum_index_cleanup", RELOPT_TYPE_BOOL,
! 		offsetof(StdRdOptions, vacuum_index_cleanup)}
  	};
  
  	options = parseRelOptions(reloptions, validate, kind, &numoptions);
--- 1413,1421 ----
  		{"vacuum_cleanup_index_scale_factor", RELOPT_TYPE_REAL,
  		offsetof(StdRdOptions, vacuum_cleanup_index_scale_factor)},
  		{"vacuum_index_cleanup", RELOPT_TYPE_BOOL,
! 		offsetof(StdRdOptions, vacuum_index_cleanup)},
! 		{"vacuum_truncate_enabled", RELOPT_TYPE_BOOL,
! 		offsetof(StdRdOptions, vacuum_truncate_enabled)}
  	};
  
  	options = parseRelOptions(reloptions, validate, kind, &numoptions);
*** a/src/backend/access/heap/vacuumlazy.c
--- b/src/backend/access/heap/vacuumlazy.c
***************
*** 165,171 **** 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 void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
  static BlockNumber count_nondeletable_pages(Relation onerel,
  						 LVRelStats *vacrelstats);
--- 165,171 ----
  				   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 void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
  static BlockNumber count_nondeletable_pages(Relation onerel,
  						 LVRelStats *vacrelstats);
***************
*** 306,312 **** heap_vacuum_rel(Relation onerel, VacuumParams *params,
  	/*
  	 * Optionally truncate the relation.
  	 */
! 	if (should_attempt_truncation(vacrelstats))
  		lazy_truncate_heap(onerel, vacrelstats);
  
  	/* Report that we are now doing final cleanup */
--- 306,312 ----
  	/*
  	 * Optionally truncate the relation.
  	 */
! 	if (should_attempt_truncation(onerel, vacrelstats))
  		lazy_truncate_heap(onerel, vacrelstats);
  
  	/* Report that we are now doing final cleanup */
***************
*** 660,666 **** 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(vacrelstats))
  
  		pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
  
--- 660,666 ----
  
  		/* see note above about forcing scanning of last page */
  #define FORCE_CHECK_PAGE() \
! 		(blkno == nblocks - 1 && should_attempt_truncation(onerel, vacrelstats))
  
  		pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
  
***************
*** 1869,1878 **** 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)
  {
  	BlockNumber possibly_freeable;
  
  	possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
  	if (possibly_freeable > 0 &&
  		(possibly_freeable >= REL_TRUNCATE_MINIMUM ||
--- 1869,1882 ----
   * careful to depend only on fields that lazy_scan_heap updates on-the-fly.
   */
  static bool
! should_attempt_truncation(Relation rel, LVRelStats *vacrelstats)
  {
  	BlockNumber possibly_freeable;
  
+ 	if (rel->rd_options != NULL &&
+ 		((StdRdOptions *) rel->rd_options)->vacuum_truncate_enabled == false)
+ 		return false;
+ 
  	possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
  	if (possibly_freeable > 0 &&
  		(possibly_freeable >= REL_TRUNCATE_MINIMUM ||
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
***************
*** 1056,1064 **** static const char *const table_storage_parameters[] = {
--- 1056,1066 ----
  	"toast.autovacuum_vacuum_scale_factor",
  	"toast.autovacuum_vacuum_threshold",
  	"toast.log_autovacuum_min_duration",
+ 	"toast.vacuum_truncate_enabled",
  	"toast_tuple_target",
  	"user_catalog_table",
  	"vacuum_index_cleanup",
+ 	"vacuum_truncate_enabled",
  	NULL
  };
  
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 267,272 **** typedef struct StdRdOptions
--- 267,274 ----
  	bool		user_catalog_table; /* use as an additional catalog relation */
  	int			parallel_workers;	/* max number of parallel workers */
  	bool		vacuum_index_cleanup;	/* enables index vacuuming and cleanup */
+ 	bool		vacuum_truncate_enabled;	/* enables vacuum to truncate
+ 											 * a relation */
  } StdRdOptions;
  
  #define HEAP_MIN_FILLFACTOR			10
*** a/src/test/regress/expected/reloptions.out
--- b/src/test/regress/expected/reloptions.out
***************
*** 87,92 **** SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND
--- 87,139 ----
  -- RESET fails if a value is specified
  ALTER TABLE reloptions_test RESET (fillfactor=12);
  ERROR:  RESET must not include values for parameters
+ -- Test vacuum_truncate_enabled option
+ DROP TABLE reloptions_test;
+ CREATE TABLE reloptions_test(i INT NOT NULL, j text)
+ 	WITH (vacuum_truncate_enabled=false,
+ 	toast.vacuum_truncate_enabled=false,
+ 	autovacuum_enabled=false);
+ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+                         reloptions                        
+ ----------------------------------------------------------
+  {vacuum_truncate_enabled=false,autovacuum_enabled=false}
+ (1 row)
+ 
+ INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+ ERROR:  null value in column "i" violates not-null constraint
+ DETAIL:  Failing row contains (null, null).
+ VACUUM reloptions_test;
+ SELECT pg_relation_size('reloptions_test') > 0;
+  ?column? 
+ ----------
+  t
+ (1 row)
+ 
+ SELECT reloptions FROM pg_class WHERE oid =
+ 	(SELECT reltoastrelid FROM pg_class
+ 	WHERE oid = 'reloptions_test'::regclass);
+            reloptions            
+ ---------------------------------
+  {vacuum_truncate_enabled=false}
+ (1 row)
+ 
+ ALTER TABLE reloptions_test RESET (vacuum_truncate_enabled);
+ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+          reloptions         
+ ----------------------------
+  {autovacuum_enabled=false}
+ (1 row)
+ 
+ INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+ ERROR:  null value in column "i" violates not-null constraint
+ DETAIL:  Failing row contains (null, 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)
*** a/src/test/regress/sql/reloptions.sql
--- b/src/test/regress/sql/reloptions.sql
***************
*** 52,57 **** SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND
--- 52,79 ----
  -- RESET fails if a value is specified
  ALTER TABLE reloptions_test RESET (fillfactor=12);
  
+ -- Test vacuum_truncate_enabled option
+ DROP TABLE reloptions_test;
+ 
+ CREATE TABLE reloptions_test(i INT NOT NULL, j text)
+ 	WITH (vacuum_truncate_enabled=false,
+ 	toast.vacuum_truncate_enabled=false,
+ 	autovacuum_enabled=false);
+ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+ VACUUM reloptions_test;
+ SELECT pg_relation_size('reloptions_test') > 0;
+ 
+ SELECT reloptions FROM pg_class WHERE oid =
+ 	(SELECT reltoastrelid FROM pg_class
+ 	WHERE oid = 'reloptions_test'::regclass);
+ 
+ ALTER TABLE reloptions_test RESET (vacuum_truncate_enabled);
+ SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass;
+ INSERT INTO reloptions_test VALUES (1, NULL), (NULL, NULL);
+ VACUUM reloptions_test;
+ SELECT pg_relation_size('reloptions_test') = 0;
+ 
  -- Test toast.* options
  DROP TABLE reloptions_test;
  
