From 6f48dfceb7e36c87b2b2a9bd6fd313f438314577 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <bossartn@amazon.com>
Date: Sat, 5 Dec 2020 05:14:46 +0000
Subject: [PATCH v5 1/1] Add SPREAD option to CHECKPOINT.

---
 doc/src/sgml/ref/checkpoint.sgml         | 50 ++++++++++++++++++++++++++++++--
 src/backend/parser/gram.y                |  9 +++++-
 src/backend/postmaster/checkpointer.c    | 43 +++++++++++++++++++++++++++
 src/backend/tcop/utility.c               |  8 +----
 src/include/nodes/parsenodes.h           |  1 +
 src/include/postmaster/bgwriter.h        |  2 ++
 src/test/regress/expected/checkpoint.out |  9 ++++++
 src/test/regress/parallel_schedule       |  9 +++---
 src/test/regress/serial_schedule         |  1 +
 src/test/regress/sql/checkpoint.sql      |  7 +++++
 10 files changed, 125 insertions(+), 14 deletions(-)
 create mode 100644 src/test/regress/expected/checkpoint.out
 create mode 100644 src/test/regress/sql/checkpoint.sql

diff --git a/doc/src/sgml/ref/checkpoint.sgml b/doc/src/sgml/ref/checkpoint.sgml
index 2afee6d7b5..2b1e56fbd7 100644
--- a/doc/src/sgml/ref/checkpoint.sgml
+++ b/doc/src/sgml/ref/checkpoint.sgml
@@ -21,7 +21,11 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-CHECKPOINT
+CHECKPOINT [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ]
+
+<phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
+
+    SPREAD [ <replaceable class="parameter">boolean</replaceable> ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -37,7 +41,7 @@ CHECKPOINT
   </para>
 
   <para>
-   The <command>CHECKPOINT</command> command forces an immediate
+   The <command>CHECKPOINT</command> command forces a
    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"/>).
@@ -51,11 +55,53 @@ CHECKPOINT
    rather than writing a new checkpoint.
   </para>
 
+  <para>
+   Note that the server may consolidate concurrently requested checkpoints or
+   restartpoints.  Such consolidated requests will contain a combined set of
+   options.  For example, if one session requested a spread checkpoint and
+   another session requested a fast checkpoint, the server may combine these
+   requests and perform one fast checkpoint.
+  </para>
+
   <para>
    Only superusers can call <command>CHECKPOINT</command>.
   </para>
  </refsect1>
 
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>SPREAD</literal></term>
+    <listitem>
+     <para>
+      Specifies that checkpoint activity should be throttled based on the
+      setting for the <xref linkend="guc-checkpoint-completion-target"/>
+      parameter.  If the option is turned off, <command>CHECKPOINT</command>
+      creates a checkpoint or restartpoint as fast as possible.  By default,
+      <literal>SPREAD</literal> is turned off, and the checkpoint activity is
+      not throttled.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">boolean</replaceable></term>
+    <listitem>
+     <para>
+      Specifies whether the selected option should be turned on or off.
+      You can write <literal>TRUE</literal>, <literal>ON</literal>, or
+      <literal>1</literal> to enable the option, and <literal>FALSE</literal>,
+      <literal>OFF</literal>, or <literal>0</literal> to disable it.  The
+      <replaceable class="parameter">boolean</replaceable> value can also
+      be omitted, in which case <literal>TRUE</literal> is assumed.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
  <refsect1>
   <title>Compatibility</title>
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ecff4cd2ac..c5b7a74c20 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1773,7 +1773,14 @@ CheckPointStmt:
 			CHECKPOINT
 				{
 					CheckPointStmt *n = makeNode(CheckPointStmt);
-					$$ = (Node *)n;
+					n->options = NIL;
+					$$ = (Node *) n;
+				}
+			| CHECKPOINT '(' utility_option_list ')'
+				{
+					CheckPointStmt *n = makeNode(CheckPointStmt);
+					n->options = $3;
+					$$ = (Node *) n;
 				}
 		;
 
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 429c8010ef..d08683cb23 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -41,6 +41,7 @@
 
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
+#include "commands/defrem.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -895,6 +896,48 @@ CheckpointerShmemInit(void)
 	}
 }
 
+/*
+ * ExecCheckPoint
+ *		Primary entry point for CHECKPOINT commands
+ */
+void
+ExecCheckPoint(ParseState *pstate, CheckPointStmt *stmt)
+{
+	ListCell *lc;
+	bool spread = false;
+	int flags;
+
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("must be superuser to do CHECKPOINT")));
+
+	foreach(lc, stmt->options)
+	{
+		DefElem *opt = (DefElem *) lfirst(lc);
+
+		if (strcmp(opt->defname, "spread") == 0)
+			spread = defGetBoolean(opt);
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("unrecognized CHECKPOINT option \"%s\"", opt->defname),
+					 parser_errposition(pstate, opt->location)));
+	}
+
+	/*
+	 * CHECKPOINT_IMMEDIATE is set unless the SPREAD option is enabled.
+	 * CHECKPOINT_WAIT is always set so that CHECKPOINT blocks until the
+	 * checkpoint or restartpoint completes.  CHECKPOINT_FORCE is set unless we
+	 * are in recovery, because it is not possible to force a restartpoint.
+	 */
+	flags = CHECKPOINT_WAIT |
+			(RecoveryInProgress() ? 0 : CHECKPOINT_FORCE) |
+			(spread ? 0 : CHECKPOINT_IMMEDIATE);
+
+	RequestCheckpoint(flags);
+}
+
 /*
  * RequestCheckpoint
  *		Called in backend processes to request a checkpoint
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a42ead7d69..fc8bb19429 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -907,13 +907,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			break;
 
 		case T_CheckPointStmt:
-			if (!superuser())
-				ereport(ERROR,
-						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-						 errmsg("must be superuser to do CHECKPOINT")));
-
-			RequestCheckpoint(CHECKPOINT_IMMEDIATE | CHECKPOINT_WAIT |
-							  (RecoveryInProgress() ? 0 : CHECKPOINT_FORCE));
+			ExecCheckPoint(pstate, (CheckPointStmt *) parsetree);
 			break;
 
 		case T_ReindexStmt:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ec14fc2036..313a3a6d43 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3292,6 +3292,7 @@ typedef struct RefreshMatViewStmt
 typedef struct CheckPointStmt
 {
 	NodeTag		type;
+	List	   *options;		/* list of DefElem nodes */
 } CheckPointStmt;
 
 /* ----------------------
diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h
index 0a5708b32e..8124b8961d 100644
--- a/src/include/postmaster/bgwriter.h
+++ b/src/include/postmaster/bgwriter.h
@@ -15,6 +15,7 @@
 #ifndef _BGWRITER_H
 #define _BGWRITER_H
 
+#include "parser/parse_node.h"
 #include "storage/block.h"
 #include "storage/relfilenode.h"
 #include "storage/smgr.h"
@@ -30,6 +31,7 @@ extern double CheckPointCompletionTarget;
 extern void BackgroundWriterMain(void) pg_attribute_noreturn();
 extern void CheckpointerMain(void) pg_attribute_noreturn();
 
+extern void ExecCheckPoint(ParseState *pstate, CheckPointStmt *stmt);
 extern void RequestCheckpoint(int flags);
 extern void CheckpointWriteDelay(int flags, double progress);
 
diff --git a/src/test/regress/expected/checkpoint.out b/src/test/regress/expected/checkpoint.out
new file mode 100644
index 0000000000..5cac30844a
--- /dev/null
+++ b/src/test/regress/expected/checkpoint.out
@@ -0,0 +1,9 @@
+-- CHECKPOINT
+CHECKPOINT;
+CHECKPOINT (SPREAD);
+CHECKPOINT (SPREAD 2);
+ERROR:  spread requires a Boolean value
+CHECKPOINT (NONEXISTENT);
+ERROR:  unrecognized CHECKPOINT option "nonexistent"
+LINE 1: CHECKPOINT (NONEXISTENT);
+                    ^
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b..354dda263f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -5,10 +5,11 @@
 # this limits the number of connections needed to run the tests.
 # ----------
 
-# run tablespace by itself, and first, because it forces a checkpoint;
-# we'd prefer not to have checkpoints later in the tests because that
-# interferes with crash-recovery testing.
-test: tablespace
+# ----------
+# Run tests that force checkpoints first.  We'd prefer to not have checkpoints
+# later in the tests because that interferes with crash-recovery testing.
+# ----------
+test: checkpoint tablespace
 
 # ----------
 # The first group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f..49901521f4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,5 +1,6 @@
 # src/test/regress/serial_schedule
 # This should probably be in an order similar to parallel_schedule.
+test: checkpoint
 test: tablespace
 test: boolean
 test: char
diff --git a/src/test/regress/sql/checkpoint.sql b/src/test/regress/sql/checkpoint.sql
new file mode 100644
index 0000000000..fcc5a20767
--- /dev/null
+++ b/src/test/regress/sql/checkpoint.sql
@@ -0,0 +1,7 @@
+-- CHECKPOINT
+
+CHECKPOINT;
+CHECKPOINT (SPREAD);
+
+CHECKPOINT (SPREAD 2);
+CHECKPOINT (NONEXISTENT);
-- 
2.16.6

