Hi,

Here's a small patch re-introducing the option to use spread checkpoints
(instead of always using CHECKPOINT_FAST) for online checksum changes.

The version v20251201 posted in [1] supported this, but the next patch
version was without checkpoints and so removed the "fast" parameter too.
Then we realized the checkpoints are actually needed, but were added
back to keep it as simple as possible. Or maybe it was an omission, not
sure, and there's no explanation on the thread.

I recall someone claiming always doing fast checkpoints is fine, because
we've already written the whole database into WAL anyway, and on large
databases that's likely way more expensive than a single checkpoint. I
don't buy that, for two reasons:

- We do have throttling for the rewrite phase, thanks to the cost_limit
and cost_delay parameters. So we can effectively throttle it, to reduce
impact of the checksums change. In which case the "fast" checkpoint can
be way more disruptive.

- We need to do checkpoints even when "disabling" checksums, in which
case we don't rewrite any data pages (or WAL-log anything), we just need
to persist the new checksum state. Which just makes the fast checkpoint
relatively more disruptive.

The attached patch is mostly extracted from v20251201, and adds the
"fast" parameter back to pg_{enable,disable}_data_checksums.

I have two open questions regarding it:

1) What should be the default? I've used fast=true, mostly because
that's what PG19 is going to do (fast checkpoints by default). It's also
somewhat consistent with e.g. VACUUM which does no throttling by
default. But I assume most production uses would want fast=false?

2) I haven't adjusted the TAP tests. We could use fast=false in a couple
of the test_checksums tests, but I'm not sure it's worth it and it makes
it way more time consuming.

We could reduce checkpoint_timeout to something very aggressive. And in
fact that's what I did locally with the TAP tests I posted in [2]. But
I'm still not convinced it's worth it - the checkpoints are still
synchronous, of course.


regards

[1]
https://www.postgresql.org/message-id/477897AE-1314-4724-9694-0BABC4F4ABDA%40yesql.se

[2]
https://www.postgresql.org/message-id/9e1331e1-93a0-4e27-934a-17b89342be4d%40vondra.me

-- 
Tomas Vondra
From 4eba7bbb739f5e7b95c4909d1cee2a437d8c6eb3 Mon Sep 17 00:00:00 2001
From: test <test>
Date: Sun, 3 May 2026 00:40:17 +0200
Subject: [PATCH 1/2] add fast parameter to enable/disable checksums

XXX Should this default to true or false? Most production systems would
probably want 'false' to reduce impact, but PG19 does fast checkpoints
by default.
---
 doc/src/sgml/func/func-admin.sgml           | 14 ++++++++++--
 src/backend/access/transam/xlog.c           | 23 ++++++++++++++------
 src/backend/postmaster/datachecksum_state.c | 24 +++++++++++++++------
 src/include/access/xlog.h                   |  4 ++--
 src/include/catalog/pg_proc.dat             | 11 +++++-----
 src/include/postmaster/datachecksum_state.h |  3 ++-
 6 files changed, 56 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 24ecb46542e..06360ea1b95 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>, <parameter>cost_limit</parameter> <type>int</type></optional>, <parameter>fast</parameter> <type>bool</type></optional> )
         <returnvalue>void</returnvalue>
        </para>
        <para>
@@ -3175,6 +3175,11 @@ 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>
+        If <parameter>fast</parameter> is specified as <literal>true</literal>
+        then a fast checkpoint will be issued when data checksums have been
+        enabled, which may cause a spike in I/O.
+       </para>
        </entry>
       </row>
 
@@ -3183,7 +3188,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></optional> )
         <returnvalue>void</returnvalue>
        </para>
        <para>
@@ -3193,6 +3198,11 @@ 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>
+        If <parameter>fast</parameter> is specified as <literal>true</literal>
+        then a fast checkpoint will be issued when data checksums have been
+        enabled, which may cause a spike in I/O.
+       </para>
        </entry>
       </row>
      </tbody>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 18d5dee06e0..6b8b48fa4ce 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4803,9 +4803,10 @@ SetDataChecksumsOnInProgress(void)
  * state transition.
  */
 void
-SetDataChecksumsOn(void)
+SetDataChecksumsOn(bool fast)
 {
 	uint64		barrier;
+	int			flags;
 
 	SpinLockAcquire(&XLogCtl->info_lck);
 
@@ -4820,7 +4821,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;
 	}
 
@@ -4850,7 +4851,11 @@ SetDataChecksumsOn(void)
 	MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 	END_CRIT_SECTION();
 
-	RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+	flags = CHECKPOINT_FORCE | CHECKPOINT_WAIT;
+	if (fast)
+		flags |= CHECKPOINT_FAST;
+
+	RequestCheckpoint(flags);
 	WaitForProcSignalBarrier(barrier);
 }
 
@@ -4868,9 +4873,15 @@ SetDataChecksumsOn(void)
  * 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);
 
@@ -4912,7 +4923,7 @@ SetDataChecksumsOff(void)
 		MyProc->delayChkptFlags &= ~DELAY_CHKPT_START;
 		END_CRIT_SECTION();
 
-		RequestCheckpoint(CHECKPOINT_FORCE | CHECKPOINT_WAIT | CHECKPOINT_FAST);
+		RequestCheckpoint(flags);
 		WaitForProcSignalBarrier(barrier);
 
 		/*
@@ -4950,7 +4961,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 d0d6acdd6a2..286096217d9 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -291,6 +291,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
@@ -318,6 +319,7 @@ typedef struct DataChecksumsStateStruct
 	DataChecksumsWorkerOperation operation;
 	int			cost_delay;
 	int			cost_limit;
+	bool		fast_checkpoint;
 
 	/*
 	 * Signaling between the launcher and the worker process.
@@ -509,6 +511,8 @@ AbsorbDataChecksumsBarrier(ProcSignalBarrierType barrier)
 Datum
 disable_data_checksums(PG_FUNCTION_ARGS)
 {
+	bool		fast = PG_GETARG_BOOL(0);
+
 	PreventCommandDuringRecovery("pg_disable_data_checksums()");
 
 	if (!superuser())
@@ -516,7 +520,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();
 }
 
@@ -530,6 +534,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()");
 
@@ -548,7 +553,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();
 }
@@ -568,7 +573,8 @@ enable_data_checksums(PG_FUNCTION_ARGS)
 void
 StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 								 int cost_delay,
-								 int cost_limit)
+								 int cost_limit,
+								 bool fast)
 {
 	BackgroundWorker bgw;
 	BackgroundWorkerHandle *bgw_handle;
@@ -588,6 +594,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;
@@ -938,7 +945,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;
@@ -1081,6 +1088,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);
 
 	/*
@@ -1139,7 +1147,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"));
@@ -1151,7 +1159,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"));
 	}
@@ -1179,6 +1187,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;
 	}
@@ -1268,7 +1277,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"),
@@ -1606,6 +1615,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 fa9ae79082b..fed5fba4c0c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12457,14 +12457,15 @@
 { oid => '9258',
   descr => 'disable data checksums',
   proname => 'pg_disable_data_checksums', provolatile => 'v', prorettype => 'void',
-  proparallel => 'r', prosrc => 'disable_data_checksums', proargtypes => '',
-  proacl => '{POSTGRES=X}'},
+  proparallel => 'r', prosrc => 'disable_data_checksums', proargtypes => 'bool',
+  proallargtypes => '{bool}', proargmodes => '{i}', proargnames => '{fast}',
+  proargdefaults => '{true}', proacl => '{POSTGRES=X}'},
 { oid => '9257',
   descr => 'enable data checksums',
   proname => 'pg_enable_data_checksums', provolatile => 'v', prorettype => 'void',
-  proparallel => 'r', proargtypes => 'int4 int4', proallargtypes => '{int4,int4}',
-  proargmodes => '{i,i}', proargnames => '{cost_delay,cost_limit}',
-  proargdefaults => '{0,100}', prosrc => 'enable_data_checksums',
+  proparallel => 'r', 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/include/postmaster/datachecksum_state.h b/src/include/postmaster/datachecksum_state.h
index 2a1ae10d55d..6bd975c0670 100644
--- a/src/include/postmaster/datachecksum_state.h
+++ b/src/include/postmaster/datachecksum_state.h
@@ -45,7 +45,8 @@ void		EmitAndWaitDataChecksumsBarrier(uint32 state);
 /* Start the background processes for enabling or disabling checksums */
 void		StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
 											 int cost_delay,
-											 int cost_limit);
+											 int cost_limit,
+											 bool fast);
 
 /* Background worker entrypoints */
 void		DataChecksumsWorkerLauncherMain(Datum arg);
-- 
2.54.0

Reply via email to