From c7365281d57cfb3428ffba6ae1b7a1ada42e5ebc Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Thu, 10 Jul 2025 10:30:35 -0500
Subject: [PATCH v9 4/5] Add MODE option to CHECKPOINT command.

This option may be set to FAST (the default) to request the
checkpoint be completed as fast as possible, or SPREAD to request
the checkpoint be spread over a longer interval (based on the
checkpoint-related configuration parameters).  Note that the server
may consolidate the options for concurrently requested checkpoints.
For example, if one session requests a "fast" checkpoint and
another requests a "spread" checkpoint, the server may perform one
"fast" checkpoint.

Author: Christoph Berg <myon@debian.org>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Fujii Masao <masao.fujii@oss.nttdata.com>
Reviewed-by: Laurenz Albe <laurenz.albe@cybertec.at>
Reviewed-by: Dilip Kumar <dilipbalaut@gmail.com>
Discussion: https://postgr.es/m/aDnaKTEf-0dLiEfz%40msg.df7cb.de
---
 doc/src/sgml/ref/checkpoint.sgml      | 35 ++++++++++++++++++++++++---
 src/backend/postmaster/checkpointer.c | 28 +++++++++++++++++----
 src/bin/psql/tab-complete.in.c        | 13 ++++++++++
 src/test/regress/expected/stats.out   |  9 +++++--
 src/test/regress/sql/stats.sql        |  6 +++--
 5 files changed, 79 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/ref/checkpoint.sgml b/doc/src/sgml/ref/checkpoint.sgml
index fad5e982d03..36a9e323f44 100644
--- a/doc/src/sgml/ref/checkpoint.sgml
+++ b/doc/src/sgml/ref/checkpoint.sgml
@@ -24,6 +24,8 @@ PostgreSQL documentation
 CHECKPOINT [ ( option [, ...] ) ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+    MODE { FAST | SPREAD }
 </synopsis>
  </refsynopsisdiv>
 
@@ -39,14 +41,24 @@ CHECKPOINT [ ( option [, ...] ) ]
   </para>
 
   <para>
-   The <command>CHECKPOINT</command> command forces a fast
+   By default, the <command>CHECKPOINT</command> command forces a fast
    checkpoint when the command is issued, without waiting for a
    regular checkpoint scheduled by the system (controlled by the settings in
    <xref linkend="runtime-config-wal-checkpoints"/>).
+   To request the checkpoint be spread over a longer interval, set the
+   <literal>MODE</literal> option to <literal>SPREAD</literal>.
    <command>CHECKPOINT</command> is not intended for use during normal
    operation.
   </para>
 
+  <para>
+   The server may consolidate concurrently requested checkpoints.  Such
+   consolidated requests will contain a combined set of options.  For example,
+   if one session requests a fast checkpoint and another requests a spread
+   checkpoint, the server may combine those requests and perform one fast
+   checkpoint.
+  </para>
+
   <para>
    If executed during recovery, the <command>CHECKPOINT</command> command
    will force a restartpoint (see <xref linkend="wal-configuration"/>)
@@ -63,8 +75,25 @@ CHECKPOINT [ ( option [, ...] ) ]
  <refsect1>
   <title>Parameters</title>
 
-  <para>
-  </para>
+  <variablelist>
+   <varlistentry>
+    <term><literal>MODE</literal></term>
+    <listitem>
+     <para>
+      When set to <literal>FAST</literal>, which is the default, the requested
+      checkpoint will be completed as fast as possible, which may result in a
+      significantly higher rate of I/O during the checkpoint.
+     </para>
+     <para>
+      <literal>MODE</literal> can also be set to <literal>SPREAD</literal> to
+      request the checkpoint be spread over a longer interval (controlled via
+      the settings in <xref linkend="runtime-config-wal-checkpoints"/>), like a
+      regular checkpoint scheduled by the system.  This can reduce the rate of
+      I/O during the checkpoint.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index dc01f2382f1..9d77269a374 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -43,6 +43,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
+#include "commands/defrem.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -987,11 +988,28 @@ CheckpointerShmemInit(void)
 void
 ExecCheckpoint(ParseState *pstate, CheckPointStmt *stmt)
 {
+	bool		fast = true;
+
 	foreach_ptr(DefElem, opt, stmt->options)
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("unrecognized CHECKPOINT option \"%s\"", opt->defname),
-				 parser_errposition(pstate, opt->location)));
+	{
+		if (strcmp(opt->defname, "mode") == 0)
+		{
+			char	   *mode = defGetString(opt);
+
+			if (strcmp(mode, "spread") == 0)
+				fast = false;
+			else if (strcmp(mode, "fast") != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("unrecognized MODE option \"%s\"", mode),
+						 parser_errposition(pstate, opt->location)));
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unrecognized CHECKPOINT option \"%s\"", opt->defname),
+					 parser_errposition(pstate, opt->location)));
+	}
 
 	if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINT))
 		ereport(ERROR,
@@ -1003,7 +1021,7 @@ ExecCheckpoint(ParseState *pstate, CheckPointStmt *stmt)
 						   "pg_checkpoint")));
 
 	RequestCheckpoint(CHECKPOINT_WAIT |
-					  CHECKPOINT_FAST |
+					  (fast ? CHECKPOINT_FAST : 0) |
 					  (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
 }
 
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 089fe367d9f..a7db04efd93 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -3156,6 +3156,19 @@ match_previous_words(int pattern_id,
 /* CHECKPOINT */
 	else if (Matches("CHECKPOINT"))
 		COMPLETE_WITH("(");
+	else if (HeadMatches("CHECKPOINT", "(*") &&
+			 !HeadMatches("CHECKPOINT", "(*)"))
+	{
+		/*
+		 * This fires if we're in an unfinished parenthesized option list.
+		 * get_previous_words treats a completed parenthesized option list as
+		 * one word, so the above test is correct.
+		 */
+		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
+			COMPLETE_WITH("MODE");
+		else if (TailMatches("MODE"))
+			COMPLETE_WITH("FAST", "SPREAD");
+	}
 /* CLOSE */
 	else if (Matches("CLOSE"))
 		COMPLETE_WITH_QUERY_PLUS(Query_for_list_of_cursors,
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index 9b865ae5f6c..b4df9ad5960 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -927,12 +927,17 @@ DROP TABLE test_stats_temp;
 -- of the checkpoint. But after a second checkpoint we'll see at least the
 -- results of the first.
 --
--- While at it, test checkpoint options.
+-- While at it, test checkpoint options.  Note that we don't test MODE SPREAD
+-- because it would prolong the test.
 CHECKPOINT (WRONG);
 ERROR:  unrecognized CHECKPOINT option "wrong"
 LINE 1: CHECKPOINT (WRONG);
                     ^
-CHECKPOINT;
+CHECKPOINT (MODE WRONG);
+ERROR:  unrecognized MODE option "wrong"
+LINE 1: CHECKPOINT (MODE WRONG);
+                    ^
+CHECKPOINT (MODE FAST);
 CHECKPOINT;
 SELECT num_requested > :rqst_ckpts_before FROM pg_stat_checkpointer;
  ?column? 
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index 97b50926aa6..0868b250a64 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -440,9 +440,11 @@ DROP TABLE test_stats_temp;
 -- of the checkpoint. But after a second checkpoint we'll see at least the
 -- results of the first.
 --
--- While at it, test checkpoint options.
+-- While at it, test checkpoint options.  Note that we don't test MODE SPREAD
+-- because it would prolong the test.
 CHECKPOINT (WRONG);
-CHECKPOINT;
+CHECKPOINT (MODE WRONG);
+CHECKPOINT (MODE FAST);
 CHECKPOINT;
 
 SELECT num_requested > :rqst_ckpts_before FROM pg_stat_checkpointer;
-- 
2.39.5 (Apple Git-154)

