On 7/8/26 15:30, Daniel Gustafsson wrote:
>> On 8 Jul 2026, at 13:53, Ewan Young <[email protected]> wrote:
> 
>> In func-admin.sgml the signature of pg_disable_data_checksums documents
>> the default as false:
>>
>> pg_disable_data_checksums ( fast bool DEFAULT false )
>> but the actual default is true. pg_proc.dat sets
> 
> Nice catch and +1, I missed that one.
> 

Yep, good catch. I went between true/false defaults a couple times, and
forgot to update all the places. The v4 fixes this, along with some
minor comment improvements.

I think it's ready, so I'm planning to go over it once more and push it
soon-ish.

regards

-- 
Tomas Vondra
From 5f753bd304523b53ec9852b3a89cf663d0423342 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Mon, 6 Jul 2026 11:31:19 +0200
Subject: [PATCH v4] Allow using spread checkpoints when enabling/disabling
 checksums

This commit allows using spread (throttled) checkpoints while enabling
or disabling data checksums in a running cluster, to limit the impact on
the rest of the system.

While enabling checksums, the process has to rewrite all data pages, and
then perform perform checkpoints at certain points. It was possible to
throttle the rewriting of data pages, to limit the impact on the rest of
the cluster, the ckeckpoints were always issued as CHECKPOINT_FAST. This
can be very disruptive.

When disabling checksums, it's not necessary to rewrite any data pages,
but the checkpoint may still trigger a lot of I/O. So a seemingly cheap
operation (disabling checksums) may get rather disruptive.

This commit introduces a new "fast" parameter for functions initiating
the checksum state change. The default "fast=true" means the requested
checkpoints are fast.

Original patch by Daniel Gustafsson, part of the f19c0eccae96 work, but
removed to reduce scope of the initial commit. Revided by me, with some
minor cleanups and improvements.

Author: Tomas Vondra <[email protected]>
Author: Daniel Gustafsson <[email protected]>
Reviewed-by: Daniel Gustafsson <[email protected]>
Reviewed-by: Ewan Young <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
 doc/src/sgml/func/func-admin.sgml             | 22 +++++++++++--
 src/backend/access/transam/xlog.c             | 31 +++++++++++++++----
 src/backend/postmaster/datachecksum_state.c   | 30 ++++++++++++------
 src/include/access/xlog.h                     |  4 +--
 src/include/catalog/pg_proc.dat               | 12 ++++---
 .../test_checksums/t/DataChecksums/Utils.pm   | 24 ++++++++++++--
 6 files changed, 96 insertions(+), 27 deletions(-)

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 0eae1c1f616..985a866e812 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -3158,7 +3158,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <indexterm>
          <primary>pg_enable_data_checksums</primary>
         </indexterm>
-        <function>pg_enable_data_checksums</function> ( <optional><parameter>cost_delay</parameter> <type>int</type>, <parameter>cost_limit</parameter> <type>int</type></optional> )
+        <function>pg_enable_data_checksums</function> ( <optional><parameter>cost_delay</parameter> <type>int</type> <literal>DEFAULT</literal> <literal>0</literal>, <parameter>cost_limit</parameter> <type>int</type> <literal>DEFAULT</literal> <literal>100</literal>, <parameter>fast</parameter> <type>bool</type> <literal>DEFAULT</literal> <literal>true</literal></optional> )
         <returnvalue>void</returnvalue>
        </para>
        <para>
@@ -3175,6 +3175,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         specified, the process is throttled using the same principles as
         <link linkend="runtime-config-resource-vacuum-cost">Cost-based Vacuum Delay</link>.
        </para>
+       <para>
+        The <parameter>fast</parameter> specifies whether the checkpoints performed
+        are fast or spread.
+        When set to <literal>true</literal> the checkpoints are fast, which may cause
+        a spike in I/O. When set to <literal>false</literal>, the checkpoints are
+        spread (throttled), which may increase the time needed to enable checksums,
+        but it limits the impact on the system.
+        The default value is <literal>true</literal>.
+       </para>
        </entry>
       </row>
 
@@ -3183,7 +3192,7 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         <indexterm>
          <primary>pg_disable_data_checksums</primary>
         </indexterm>
-        <function>pg_disable_data_checksums</function> ()
+        <function>pg_disable_data_checksums</function> ( <optional><parameter>fast</parameter> <type>bool</type> <literal>DEFAULT</literal> <literal>true</literal></optional> )
         <returnvalue>void</returnvalue>
        </para>
        <para>
@@ -3193,6 +3202,15 @@ SELECT convert_from(pg_read_binary_file('file_in_utf8.txt'), 'UTF8');
         stopped validating data checksums, the data checksum state will be
         set to <literal>off</literal>.
        </para>
+       <para>
+        The <parameter>fast</parameter> specifies whether the checkpoints performed
+        are fast or spread.
+        When set to <literal>true</literal> the checkpoints are fast, which may cause
+        a spike in I/O. When set to <literal>false</literal>, the checkpoints are
+        spread (throttled), which may increase the time needed to disable checksums,
+        but it limits the impact on the system.
+        The default value is <literal>true</literal>.
+       </para>
        </entry>
       </row>
      </tbody>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b431a921e4b..b737aa080dc 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4794,13 +4794,17 @@ SetDataChecksumsOnInProgress(void)
  * state can be changed to "on". Once the state is "on" checksums will be both
  * written and verified.
  *
+ * 'fast' determines whether the checkpoints requested before progressing to
+ * the next phase are fast (CHECKPOINT_FAST) or spread.
+ *
  * This function blocks until all backends in the cluster have acknowledged the
  * state transition.
  */
 void
-SetDataChecksumsOn(void)
+SetDataChecksumsOn(bool fast)
 {
 	uint64		barrier;
+	int			flags;
 
 	SpinLockAcquire(&XLogCtl->info_lck);
 
@@ -4814,7 +4818,7 @@ SetDataChecksumsOn(void)
 		SpinLockRelease(&XLogCtl->info_lck);
 		elog(WARNING,
 			 "cannot set data checksums to \"on\", current state is not \"inprogress-on\", disabling");
-		SetDataChecksumsOff();
+		SetDataChecksumsOff(fast);
 		return;
 	}
 
@@ -4844,7 +4848,12 @@ SetDataChecksumsOn(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
-	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+	/* determine flags for the checkpoint */
+	flags = CHECKPOINT_FORCE | CHECKPOINT_WAIT;
+	if (fast)
+		flags |= CHECKPOINT_FAST;
+
+	RequestCheckpoint(flags);
 	WaitForProcSignalBarrier(barrier);
 }
 
@@ -4858,13 +4867,23 @@ SetDataChecksumsOn(void)
  * backends which have yet to observe the state change from "on" won't get
  * validation errors on concurrently modified pages. Once all backends have
  * changed to "inprogress-off", the barrier for moving to "off" can be emitted.
+ *
+ * 'fast' determines whether the checkpoints requested before progressing to
+ * the next phase are fast (CHECKPOINT_FAST) or spread.
+ *
  * This function blocks until all backends in the cluster have acknowledged the
  * state transition.
  */
 void
-SetDataChecksumsOff(void)
+SetDataChecksumsOff(bool fast)
 {
 	uint64		barrier;
+	int			flags;
+
+	/* determine flags for the checkpoint(s) */
+	flags = CHECKPOINT_FORCE | CHECKPOINT_WAIT;
+	if (fast)
+		flags |= CHECKPOINT_FAST;
 
 	SpinLockAcquire(&XLogCtl->info_lck);
 
@@ -4906,7 +4925,7 @@ SetDataChecksumsOff(void)
 		MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 		END_CRIT_SECTION();
 
-		RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+		RequestCheckpoint(flags);
 		WaitForProcSignalBarrier(barrier);
 
 		/*
@@ -4944,7 +4963,7 @@ SetDataChecksumsOff(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
-	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+	RequestCheckpoint(flags);
 	WaitForProcSignalBarrier(barrier);
 }
 
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 73dc539836b..7a361437868 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -307,6 +307,7 @@ typedef struct DataChecksumsStateStruct
 	DataChecksumsWorkerOperation launch_operation;
 	int			launch_cost_delay;
 	int			launch_cost_limit;
+	bool		launch_fast_checkpoint;
 
 	/*
 	 * Is a launcher process currently running?  This is set by the main
@@ -340,6 +341,7 @@ typedef struct DataChecksumsStateStruct
 	DataChecksumsWorkerOperation operation;
 	int			cost_delay;
 	int			cost_limit;
+	bool		fast_checkpoint;
 
 	/*
 	 * Signaling between the launcher and the worker process. Protected by
@@ -382,7 +384,8 @@ static DataChecksumsWorkerOperation operation;
 /* Prototypes */
 static void StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 											 int cost_delay,
-											 int cost_limit);
+											 int cost_limit,
+											 bool fast);
 static void DataChecksumsShmemRequest(void *arg);
 static bool DatabaseExists(Oid dboid);
 static List *BuildDatabaseList(void);
@@ -544,6 +547,8 @@ AbsorbDataChecksumsBarrier(ProcSignalBarrierType barrier)
 Datum
 disable_data_checksums(PG_FUNCTION_ARGS)
 {
+	bool		fast = PG_GETARG_BOOL(0);
+
 	PreventCommandDuringRecovery("pg_disable_data_checksums()");
 
 	if (!superuser())
@@ -551,7 +556,7 @@ disable_data_checksums(PG_FUNCTION_ARGS)
 				errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				errmsg("must be superuser to change data checksum state"));
 
-	StartDataChecksumsWorkerLauncher(DISABLE_DATACHECKSUMS, 0, 0);
+	StartDataChecksumsWorkerLauncher(DISABLE_DATACHECKSUMS, 0, 0, fast);
 	PG_RETURN_VOID();
 }
 
@@ -565,6 +570,7 @@ enable_data_checksums(PG_FUNCTION_ARGS)
 {
 	int			cost_delay = PG_GETARG_INT32(0);
 	int			cost_limit = PG_GETARG_INT32(1);
+	bool		fast = PG_GETARG_BOOL(2);
 
 	PreventCommandDuringRecovery("pg_enable_data_checksums()");
 
@@ -583,7 +589,7 @@ enable_data_checksums(PG_FUNCTION_ARGS)
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("cost limit must be greater than zero"));
 
-	StartDataChecksumsWorkerLauncher(ENABLE_DATACHECKSUMS, cost_delay, cost_limit);
+	StartDataChecksumsWorkerLauncher(ENABLE_DATACHECKSUMS, cost_delay, cost_limit, fast);
 
 	PG_RETURN_VOID();
 }
@@ -603,7 +609,8 @@ enable_data_checksums(PG_FUNCTION_ARGS)
 static void
 StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 								 int cost_delay,
-								 int cost_limit)
+								 int cost_limit,
+								 bool fast)
 {
 	BackgroundWorker bgw;
 	BackgroundWorkerHandle *bgw_handle;
@@ -623,6 +630,7 @@ StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 	DataChecksumState->launch_operation = op;
 	DataChecksumState->launch_cost_delay = cost_delay;
 	DataChecksumState->launch_cost_limit = cost_limit;
+	DataChecksumState->launch_fast_checkpoint = fast;
 
 	/* Is the launcher already running? If so, what is it doing? */
 	running = DataChecksumState->launcher_running;
@@ -1025,7 +1033,7 @@ launcher_exit(int code, Datum arg)
 	 * the state to off since processing cannot be resumed.
 	 */
 	if (DataChecksumsInProgressOn())
-		SetDataChecksumsOff();
+		SetDataChecksumsOff(DataChecksumState->fast_checkpoint);
 
 	LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 	launcher_running = false;
@@ -1166,6 +1174,7 @@ DataChecksumsWorkerLauncherMain(Datum arg)
 	DataChecksumState->operation = operation;
 	DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
 	DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
+	DataChecksumState->fast_checkpoint = DataChecksumState->launch_fast_checkpoint;
 	LWLockRelease(DataChecksumsWorkerLock);
 
 	/*
@@ -1221,7 +1230,7 @@ again:
 		 * Data checksums have been set on all pages, set the state to on in
 		 * order to instruct backends to validate checksums on reading.
 		 */
-		SetDataChecksumsOn();
+		SetDataChecksumsOn(DataChecksumState->fast_checkpoint);
 
 		ereport(LOG,
 				errmsg("data checksums are now enabled"));
@@ -1233,7 +1242,7 @@ again:
 
 		pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
 									 PROGRESS_DATACHECKSUMS_PHASE_DISABLING);
-		SetDataChecksumsOff();
+		SetDataChecksumsOff(DataChecksumState->fast_checkpoint);
 		ereport(LOG,
 				errmsg("data checksums are now disabled"));
 	}
@@ -1261,6 +1270,7 @@ done:
 		operation = DataChecksumState->launch_operation;
 		DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
 		DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
+		DataChecksumState->fast_checkpoint = DataChecksumState->launch_fast_checkpoint;
 		LWLockRelease(DataChecksumsWorkerLock);
 		goto again;
 	}
@@ -1341,7 +1351,7 @@ ProcessAllDatabases(void)
 			 * Disable checksums on cluster, because we failed one of the
 			 * databases and this is an all or nothing process.
 			 */
-			SetDataChecksumsOff();
+			SetDataChecksumsOff(DataChecksumState->fast_checkpoint);
 			ereport(ERROR,
 					errcode(ERRCODE_INSUFFICIENT_RESOURCES),
 					errmsg("data checksums failed to get enabled in all databases, aborting"),
@@ -1704,7 +1714,8 @@ DataChecksumsWorkerMain(Datum arg)
 			break;
 		}
 		if ((DataChecksumState->launch_cost_delay != DataChecksumState->cost_delay)
-			|| (DataChecksumState->launch_cost_limit != DataChecksumState->cost_limit))
+			|| (DataChecksumState->launch_cost_limit != DataChecksumState->cost_limit)
+			|| (DataChecksumState->launch_fast_checkpoint != DataChecksumState->fast_checkpoint))
 		{
 			costs_updated = true;
 			VacuumCostDelay = DataChecksumState->launch_cost_delay;
@@ -1713,6 +1724,7 @@ DataChecksumsWorkerMain(Datum arg)
 
 			DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
 			DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
+			DataChecksumState->fast_checkpoint = DataChecksumState->launch_fast_checkpoint;
 		}
 		else
 			costs_updated = false;
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 4dd98624204..8a9e8961a6a 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -253,8 +253,8 @@ extern bool DataChecksumsOn(void);
 extern bool DataChecksumsOff(void);
 extern bool DataChecksumsInProgressOn(void);
 extern void SetDataChecksumsOnInProgress(void);
-extern void SetDataChecksumsOn(void);
-extern void SetDataChecksumsOff(void);
+extern void SetDataChecksumsOn(bool fast);
+extern void SetDataChecksumsOff(bool fast);
 extern const char *show_data_checksums(void);
 extern const char *get_checksum_state_string(uint32 state);
 extern void InitLocalDataChecksumState(void);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3cb84359adf..c7ef5a9c930 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12415,13 +12415,15 @@
 # data checksum management functions
 { oid => '6507', descr => 'disable data checksums',
   proname => 'pg_disable_data_checksums', provolatile => 'v',
-  proparallel => 'r', prorettype => 'void', proargtypes => '',
-  prosrc => 'disable_data_checksums', proacl => '{POSTGRES=X}' },
+  proparallel => 'r', prorettype => 'void', proargtypes => 'bool',
+  proallargtypes => '{bool}', proargmodes => '{i}', proargnames => '{fast}',
+  proargdefaults => '{true}', prosrc => 'disable_data_checksums',
+  proacl => '{POSTGRES=X}' },
 { oid => '6506', descr => 'enable data checksums',
   proname => 'pg_enable_data_checksums', provolatile => 'v', proparallel => 'r',
-  prorettype => 'void', proargtypes => 'int4 int4',
-  proallargtypes => '{int4,int4}', proargmodes => '{i,i}',
-  proargnames => '{cost_delay,cost_limit}', proargdefaults => '{0,100}',
+  prorettype => 'void', proargtypes => 'int4 int4 bool',
+  proallargtypes => '{int4,int4,bool}', proargmodes => '{i,i,i}',
+  proargnames => '{cost_delay,cost_limit,fast}', proargdefaults => '{0,100,true}',
   prosrc => 'enable_data_checksums', proacl => '{POSTGRES=X}' },
 
 # collation management functions
diff --git a/src/test/modules/test_checksums/t/DataChecksums/Utils.pm b/src/test/modules/test_checksums/t/DataChecksums/Utils.pm
index 3aa533d777f..77c208dd5fd 100644
--- a/src/test/modules/test_checksums/t/DataChecksums/Utils.pm
+++ b/src/test/modules/test_checksums/t/DataChecksums/Utils.pm
@@ -118,6 +118,11 @@ If defined, the function will wait for the state defined in this parameter,
 waiting timing out, before returning.  The function will wait for
 $PostgreSQL::Test::Utils::timeout_default seconds before timing out.
 
+=item fast
+
+The B<fast> determines if checkpoints will be fast or spread, default is
+'true' (which means fast checkpoints).
+
 =back
 
 =cut
@@ -130,13 +135,14 @@ sub enable_data_checksums
 	# Set sane defaults for the parameters
 	$params{cost_delay} = 0 unless (defined($params{cost_delay}));
 	$params{cost_limit} = 100 unless (defined($params{cost_limit}));
+	$params{fast} = 'true' unless (defined($params{fast}));
 
 	my $query = <<'EOQ';
-SELECT pg_enable_data_checksums(%s, %s);
+SELECT pg_enable_data_checksums(%s, %s, %s);
 EOQ
 
 	$postgresnode->safe_psql('postgres',
-		sprintf($query, $params{cost_delay}, $params{cost_limit}));
+		sprintf($query, $params{cost_delay}, $params{cost_limit}, $params{fast}));
 
 	if (defined($params{wait}))
 	{
@@ -166,6 +172,11 @@ waiting timing out, before returning.  The function will wait for
 $PostgreSQL::Test::Utils::timeout_default seconds before timing out.
 Unlike in C<enable_data_checksums> the value of the parameter is discarded.
 
+=item fast
+
+The B<fast> determines if checkpoints will be fast or spread, default is
+'true' (which means fast checkpoints).
+
 =back
 
 =cut
@@ -175,8 +186,15 @@ sub disable_data_checksums
 	my $postgresnode = shift;
 	my %params = @_;
 
+	# Set sane defaults for the parameters
+	$params{fast} = 'true' unless (defined($params{fast}));
+
+	my $query = <<'EOQ';
+SELECT pg_disable_data_checksums(%s);
+EOQ
+
 	$postgresnode->safe_psql('postgres',
-		'SELECT pg_disable_data_checksums();');
+		sprintf($query, $params{fast}));
 
 	if (defined($params{wait}))
 	{
-- 
2.54.0

Reply via email to