Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Started by Shubham Khannaabout 1 year ago52 messages
#1Shubham Khanna
khannashubham1197@gmail.com
1 attachment(s)

Hi all,

I am writing to propose the addition of the two_phase option in
pg_createsubscriber. As discussed in [1]/messages/by-id/CAA4eK1+7gRxiK3xNEH03CY3mMKxqi7BVz2k3VxxVZp8fgjHZxg@mail.gmail.com, supporting this feature
during the development of pg_createsubscriber was planned for this
version.
Currently, pg_createsubscriber creates subscriptions with the
two_phase option set to false. Enabling the two_phase option after a
subscription has been created is not straightforward, as it requires
the subscription to be disabled first. This patch aims to address this
limitation by incorporating the two_phase option into
pg_createsubscriber which will help create subscription with two_phase
option and make use of the advantages of creating subscription with
two_phase option.
The attached patch has the changes for the same.

[1]: /messages/by-id/CAA4eK1+7gRxiK3xNEH03CY3mMKxqi7BVz2k3VxxVZp8fgjHZxg@mail.gmail.com

Thanks and regards,
Shubham Khanna.

Attachments:

v1-0001-Add-support-for-enabling-disabling-two-phase-comm.patchapplication/octet-stream; name=v1-0001-Add-support-for-enabling-disabling-two-phase-comm.patchDownload
From 81ee58d06cbc6d688281acac15766619e406e82f Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v1] Add support for enabling/disabling two-phase commit in
 pg_createsubscriber

This patch introduces the '--two-phase' option to the 'pg_createsubscriber'
utility, allowing users to enable or disable two-phase commit for subscriptions
during their creation.

By default, two-phase commit is enabled if the option is provided without
arguments. Users can explicitly set it to 'on' or 'off' using '--two-phase=on'
or '--two-phase=off'.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Notably, the replication for prepared transactions functions regardless of the
initial two-phase setting on the replication slot. However, the user cannot
change the setting after the subscription is created unless a future command,
such as 'ALTER SUBSCRIPTION ... SET (two-phase = on)', is supported.
This provides flexibility for future enhancements.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--two-phase' flag and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 16 +++++
 src/bin/pg_basebackup/pg_createsubscriber.c   | 31 +++++++-
 .../t/040_pg_createsubscriber.pl              | 71 +++++++++++++++++++
 3 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..b9814e6b15 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -186,6 +186,22 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--two_phase</option></term>
+     <listitem>
+      <para>
+       Enables or disables two-phase commit for the subscription.
+       When the option is provided without a value, it defaults to
+       <literal>on</literal>. Specify <literal>on</literal> to enable or
+       <literal>off</literal> to disable.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. By default, this option is enabled unless explicitly set
+       to <literal>off</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>--config-file=<replaceable class="parameter">filename</replaceable></option></term>
      <listitem>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..0c2f3f2a84 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -56,6 +57,7 @@ struct LogicalRepInfo
 
 	bool		made_replslot;	/* replication slot was created */
 	bool		made_publication;	/* publication was created */
+	bool		two_phase;		/* two-phase option was created */
 };
 
 static void cleanup_objects_atexit(void);
@@ -227,6 +229,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --two-phase					Enable two-phase commit for the subscription\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -466,6 +469,9 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 			dbinfo[i].replslotname = NULL;
 		dbinfo[i].made_replslot = false;
 		dbinfo[i].made_publication = false;
+		/* Store two-phase option */
+		dbinfo[i].two_phase = opt->two_phase;
+
 		/* Fill subscriber attributes */
 		conninfo = concat_conninfo_dbname(sub_base_conninfo, cell->val);
 		dbinfo[i].subconninfo = conninfo;
@@ -482,6 +488,8 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
 					 dbinfo[i].subconninfo);
+		pg_log_debug("publisher(%d): two-phase: %s", i,
+					 dbinfo[i].two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1699,8 +1707,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  dbinfo->two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1881,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"two_phase", optional_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1937,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = true;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1960,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:T:U:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1997,20 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				if (optarg != NULL)
+				{
+					if (strcmp(optarg, "on") == 0)
+						opt.two_phase = true;
+					else if (strcmp(optarg, "off") == 0)
+						opt.two_phase = false;
+					else
+						pg_fatal("invalid value for --two-phase: must be 'on' or 'off'");
+				}
+				else
+					opt.two_phase = true;	/* Default to true if no argument
+											 * is provided */
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..faad1507f4 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -432,10 +432,81 @@ my $sysid_s = $node_s->safe_psql('postgres',
 	'SELECT system_identifier FROM pg_control_system()');
 ok($sysid_p != $sysid_s, 'system identifier was changed');
 
+# Set up node A as primary
+my $node_a = PostgreSQL::Test::Cluster->new('node_a');
+my $aconnstr = $node_a->connstr;
+$node_a->init(allows_streaming => 'logical');
+$node_a->append_conf(
+	'postgresql.conf', qq[
+	autovacuum = off
+	wal_level = logical
+	max_wal_senders = 10
+	max_worker_processes = 8
+	max_prepared_transactions = 10
+]);
+
+$node_a->start;
+
+# Set up node B as standby linking to node A
+$node_a->backup('backup_1');
+my $node_b = PostgreSQL::Test::Cluster->new('node_b');
+$node_b->init_from_backup($node_a, 'backup_1', has_streaming => 1);
+$node_b->append_conf(
+	'postgresql.conf', qq[
+	primary_conninfo = '$aconnstr dbname=postgres'
+	hot_standby_feedback = on
+	max_logical_replication_workers = 1
+	max_worker_processes = 8
+	max_prepared_transactions = 10
+]);
+$node_b->set_standby_mode();
+$node_b->start;
+
+my $db10 = generate_db($node_a, 'regression\\"\\', 1, 45, '\\\\"\\\\\\');
+
+# Create a table on Publisher
+$node_a->safe_psql($db10, "CREATE TABLE tab_full (a int PRIMARY KEY);");
+
+# Wait for replication to catch up
+$node_a->wait_for_catchup($node_b);
+
+$node_b->stop;
+
+# Run pg_createsubscriber on a promoted server with two_phase=on
+command_ok(
+	[
+		'pg_createsubscriber', '--verbose',
+		'--recovery-timeout', "$PostgreSQL::Test::Utils::timeout_default",
+		'--pgdata', $node_b->data_dir,
+		'--publisher-server', $node_a->connstr($db10),
+		'--subscriber-port', $node_b->port,
+		'--database', $db10,
+		'--two_phase=on'
+	],
+	'created subscription with two-phase commit enabled');
+
+# Prepare a transaction on the publisher
+$node_a->safe_psql(
+	$db10, qq[
+	BEGIN;
+	INSERT INTO tab_full SELECT generate_series(1, 10);
+	PREPARE TRANSACTION 'test_prepare';
+]);
+
+$node_b->start;
+
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_b =
+  $node_b->safe_psql($db10, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_b, qq(1), 'Prepared transaction replicated to subscriber');
+
 # clean up
 $node_p->teardown_node;
 $node_s->teardown_node;
 $node_t->teardown_node;
 $node_f->teardown_node;
+$node_a->teardown_node;
+$node_b->teardown_node;
 
 done_testing();
-- 
2.41.0.windows.3

#2vignesh C
vignesh21@gmail.com
In reply to: Shubham Khanna (#1)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, 9 Dec 2024 at 16:25, Shubham Khanna <khannashubham1197@gmail.com> wrote:

Hi all,

I am writing to propose the addition of the two_phase option in
pg_createsubscriber. As discussed in [1], supporting this feature
during the development of pg_createsubscriber was planned for this
version.

Yes this will be useful.

Currently, pg_createsubscriber creates subscriptions with the
two_phase option set to false. Enabling the two_phase option after a
subscription has been created is not straightforward, as it requires
the subscription to be disabled first. This patch aims to address this
limitation by incorporating the two_phase option into
pg_createsubscriber which will help create subscription with two_phase
option and make use of the advantages of creating subscription with
two_phase option.
The attached patch has the changes for the same.

Few comments:
1) You can keep it with no argument similar to how dry-run is handled:
@@ -1872,6 +1881,7 @@ main(int argc, char **argv)
                {"publisher-server", required_argument, NULL, 'P'},
                {"socketdir", required_argument, NULL, 's'},
                {"recovery-timeout", required_argument, NULL, 't'},
+               {"two_phase", optional_argument, NULL, 'T'},
                {"subscriber-username", required_argument, NULL, 'U'},
                {"verbose", no_argument, NULL, 'v'},
                {"version", no_argument, NULL, 'V'},
2) After making it to no argument option, if this option is set, just
set the value, strcmp's are not required:
+                       case 'T':
+                               if (optarg != NULL)
+                               {
+                                       if (strcmp(optarg, "on") == 0)
+                                               opt.two_phase = true;
+                                       else if (strcmp(optarg, "off") == 0)
+                                               opt.two_phase = false;
+                                       else
+                                               pg_fatal("invalid
value for --two-phase: must be 'on' or 'off'");
+                               }
+                               else
+                                       opt.two_phase = true;   /*
Default to true if no argument
+
                  * is provided */
+                               break;
3) You can add a link to create subscription documentation page of
two_phase option here:
+       Enables or disables two-phase commit for the subscription.
+       When the option is provided without a value, it defaults to
+       <literal>on</literal>. Specify <literal>on</literal> to enable or
+       <literal>off</literal> to disable.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. By default, this option is enabled unless explicitly set
+       to <literal>off</literal>.
4) Instead of adding new tests, can we include the prepare transaction
and prepare transaction verification to the existing tests itself?
+# Set up node A as primary
+my $node_a = PostgreSQL::Test::Cluster->new('node_a');
+my $aconnstr = $node_a->connstr;
+$node_a->init(allows_streaming => 'logical');
+$node_a->append_conf(
+       'postgresql.conf', qq[
+       autovacuum = off
+       wal_level = logical
+       max_wal_senders = 10
+       max_worker_processes = 8
+       max_prepared_transactions = 10
+]);
+
+$node_a->start;

Regards,
Vignesh

#3Hayato Kuroda (Fujitsu)
kuroda.hayato@fujitsu.com
In reply to: Shubham Khanna (#1)
RE: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Dear Shubham,

Thanks for the proposal!

I am writing to propose the addition of the two_phase option in
pg_createsubscriber. As discussed in [1], supporting this feature
during the development of pg_createsubscriber was planned for this
version.

Yes, that was the pending item.

The attached patch has the changes for the same.

There are API related comments.

01.
I think the two-phase should be off by default. Because default value for CREATE
SUBSCRIPTION command is off, and your patch breaks pre-existing style.

02.
API style should be changed: no need to require the argument. I think it is enough
to provide "enable-twophase" and "T" option which specify the two_phase to true.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

#4Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#1)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham,

Here are some review comments for the patch v1-0001.

Note -- I think Kuroda-san's idea to use a new switch like
'--enable-two-phase' would eliminate lots of my review comments below
about the inconsistencies with CREATE SUBSCRIBER, and the current
confusion about defaults and argument values etc.

======
Commit message

1.
By default, two-phase commit is enabled if the option is provided without
arguments. Users can explicitly set it to 'on' or 'off' using '--two-phase=on'
or '--two-phase=off'.

~

But you don't say what is the default if the option is omitted entirely.

~~~

2.
Notably, the replication for prepared transactions functions regardless of the
initial two-phase setting on the replication slot. However, the user cannot
change the setting after the subscription is created unless a future command,
such as 'ALTER SUBSCRIPTION ... SET (two-phase = on)', is supported.
This provides flexibility for future enhancements.

~

Typo in ALTER example with the option name /two-phase/two_phase/

~~~

3.
Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--two-phase' flag and its combinations with other options.

/flag/option/ ??

======
doc/src/sgml/ref/pg_createsubscriber.sgml

4.
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--two_phase</option></term>
+     <listitem>
+      <para>
+       Enables or disables two-phase commit for the subscription.
+       When the option is provided without a value, it defaults to
+       <literal>on</literal>. Specify <literal>on</literal> to enable or
+       <literal>off</literal> to disable.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. By default, this option is enabled unless explicitly set
+       to <literal>off</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+

4a.
Typo -- the option you made is 'two-phase', not 'two_phase'

~

4b.
When you say "By default, this option is enabled unless explicitly set
to off." I take that as meaning it is default *enabled* even when the
option is entirely omitted.

But, that would be contrary to the behaviour of a normal CREATE
SUBSCRIPTION 'two_phase' option, so I wonder why should
pg_createsubscriber have a different default. Is this intentional? IMO
if no switch is specified then I thought it should be the same as the
CREATE SUBSCRIPTION default (i.e. false).

~

4c.
This "-T" option should be moved to be adjacent (below) "-t" to keep
everything consistently in A-Z order.

======
src/bin/pg_basebackup/pg_createsubscriber.c

5.
bool made_replslot; /* replication slot was created */
bool made_publication; /* publication was created */
+ bool two_phase; /* two-phase option was created */

I am not sure what "option was created" even means. Cut/paste error?

Also, perhaps this field belongs with the 1st group of fields in this
struct, instead of with those made_xxx fields.

~~~

store_pub_sub_info:

6.
+ /* Store two-phase option */
+ dbinfo[i].two_phase = opt->two_phase;
+

The comment says the same as what the code is doing which seems
unhelpful/redundant. And if you rearrange the struct fields as
previously suggested, this assignment should also move.

~~~

7.
  pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
  dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
  dbinfo[i].subconninfo);
+ pg_log_debug("publisher(%d): two-phase: %s", i,
+ dbinfo[i].two_phase ? "true" : "false");

"two_phase" is a subscription option. So shouldn't this added
pg_log_debug info be part of the preceding pg_log_debug about the
subscription?

~~~

main:

8.
{"recovery-timeout", required_argument, NULL, 't'},
+ {"two_phase", optional_argument, NULL, 'T'},
{"subscriber-username", required_argument, NULL, 'U'},

AFAIK this option was supposed to be 'two-phase' (dash instead of
underscore is consistent with the other pg_createsubscriber options)

~~~

9.
opt.sub_username = NULL;
+ opt.two_phase = true;

As previously mentioned this default is not the same as the CREATE
SUBSCRIPTION default for 'two_phase', and I find that inconsistency to
be confusing.

~~~

10.
+ case 'T':
+ if (optarg != NULL)
+ {
+ if (strcmp(optarg, "on") == 0)
+ opt.two_phase = true;
+ else if (strcmp(optarg, "off") == 0)
+ opt.two_phase = false;
+ else
+ pg_fatal("invalid value for --two-phase: must be 'on' or 'off'");
+ }
+ else
+ opt.two_phase = true; /* Default to true if no argument
+ * is provided */
+ break;

I wonder if users familiar with the CREATE SUBSCRIPTION 'two_phase'
might find it strange that the only values accepted here are 'on' and
'off' but now all the other boolean variants.

======
.../t/040_pg_createsubscriber.pl

11.
+# Run pg_createsubscriber on a promoted server with two_phase=on
+command_ok(
+ [
+ 'pg_createsubscriber', '--verbose',
+ '--recovery-timeout', "$PostgreSQL::Test::Utils::timeout_default",
+ '--pgdata', $node_b->data_dir,
+ '--publisher-server', $node_a->connstr($db10),
+ '--subscriber-port', $node_b->port,
+ '--database', $db10,
+ '--two_phase=on'
+ ],
+ 'created subscription with two-phase commit enabled');

Hmm. I expect this should have been specified as '--two-phase=on'
(dash instead of underscore for consistency with all other switches of
pg_createsubscriber) but previous typos (e.g. #8 above) have
compounded the confusion.

======
Kind Regards,
Peter Smith.
Fujitsu Australia

#5Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#2)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, Dec 9, 2024 at 7:43 PM vignesh C <vignesh21@gmail.com> wrote:

On Mon, 9 Dec 2024 at 16:25, Shubham Khanna <khannashubham1197@gmail.com> wrote:

Hi all,

I am writing to propose the addition of the two_phase option in
pg_createsubscriber. As discussed in [1], supporting this feature
during the development of pg_createsubscriber was planned for this
version.

Yes this will be useful.

Currently, pg_createsubscriber creates subscriptions with the
two_phase option set to false. Enabling the two_phase option after a
subscription has been created is not straightforward, as it requires
the subscription to be disabled first. This patch aims to address this
limitation by incorporating the two_phase option into
pg_createsubscriber which will help create subscription with two_phase
option and make use of the advantages of creating subscription with
two_phase option.
The attached patch has the changes for the same.

Few comments:
1) You can keep it with no argument similar to how dry-run is handled:
@@ -1872,6 +1881,7 @@ main(int argc, char **argv)
{"publisher-server", required_argument, NULL, 'P'},
{"socketdir", required_argument, NULL, 's'},
{"recovery-timeout", required_argument, NULL, 't'},
+               {"two_phase", optional_argument, NULL, 'T'},
{"subscriber-username", required_argument, NULL, 'U'},
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},

Changed the argument to 'no_argument'.

2) After making it to no argument option, if this option is set, just
set the value, strcmp's are not required:
+                       case 'T':
+                               if (optarg != NULL)
+                               {
+                                       if (strcmp(optarg, "on") == 0)
+                                               opt.two_phase = true;
+                                       else if (strcmp(optarg, "off") == 0)
+                                               opt.two_phase = false;
+                                       else
+                                               pg_fatal("invalid
value for --two-phase: must be 'on' or 'off'");
+                               }
+                               else
+                                       opt.two_phase = true;   /*
Default to true if no argument
+
* is provided */
+                               break;

Updated the switch case according to the modified argument.

3) You can add a link to create subscription documentation page of
two_phase option here:
+       Enables or disables two-phase commit for the subscription.
+       When the option is provided without a value, it defaults to
+       <literal>on</literal>. Specify <literal>on</literal> to enable or
+       <literal>off</literal> to disable.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. By default, this option is enabled unless explicitly set
+       to <literal>off</literal>.

Added the link to create subscription in the documentation of
pg_createsubscriber.

4) Instead of adding new tests, can we include the prepare transaction
and prepare transaction verification to the existing tests itself?
+# Set up node A as primary
+my $node_a = PostgreSQL::Test::Cluster->new('node_a');
+my $aconnstr = $node_a->connstr;
+$node_a->init(allows_streaming => 'logical');
+$node_a->append_conf(
+       'postgresql.conf', qq[
+       autovacuum = off
+       wal_level = logical
+       max_wal_senders = 10
+       max_worker_processes = 8
+       max_prepared_transactions = 10
+]);
+
+$node_a->start;

Removed the new test case and added it to the existing test cases.

The attached Patch contains the required changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v2-0001-Add-support-for-enabling-disabling-two-phase-comm.patchapplication/octet-stream; name=v2-0001-Add-support-for-enabling-disabling-two-phase-comm.patchDownload
From 4e9fbe648773463688d2b8d1202939a5f8349420 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v2] Add support for enabling/disabling two-phase commit in
 pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable or disable two-phase
commit for subscriptions during their creation.

By default, two-phase commit is disabled if the option is provided without
arguments.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Notably, the replication for prepared transactions functions regardless of the
initial two-phase setting on the replication slot. However, the user cannot
change the setting after the subscription is created unless a future command,
such as 'ALTER SUBSCRIPTION ... SET (two_phase = on)', is supported.
This provides flexibility for future enhancements.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 16 ++++++++++++++
 src/bin/pg_basebackup/pg_createsubscriber.c   | 22 ++++++++++++++-----
 .../t/040_pg_createsubscriber.pl              | 19 +++++++++++++++-
 3 files changed, 51 insertions(+), 6 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..0fcd30db7f 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,22 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables two-phase commit for the subscription. When the option is
+       provided, it is explicitly enabled. By default, two-phase commit is
+       <literal>off</literal>.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. See the
+       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       documentation for more details.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..ce11ab7542 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -53,6 +54,7 @@ struct LogicalRepInfo
 	char	   *pubname;		/* publication name */
 	char	   *subname;		/* subscription name */
 	char	   *replslotname;	/* replication slot name */
+	bool		two_phase;		/* two-phase enabled for the subscription */
 
 	bool		made_replslot;	/* replication slot was created */
 	bool		made_publication;	/* publication was created */
@@ -227,6 +229,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase			enable two-phase commit for the subscription\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -456,6 +459,7 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 		conninfo = concat_conninfo_dbname(pub_base_conninfo, cell->val);
 		dbinfo[i].pubconninfo = conninfo;
 		dbinfo[i].dbname = cell->val;
+		dbinfo[i].two_phase = opt->two_phase;	/* Set two-phase option */
 		if (num_pubs > 0)
 			dbinfo[i].pubname = pubcell->val;
 		else
@@ -466,6 +470,7 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 			dbinfo[i].replslotname = NULL;
 		dbinfo[i].made_replslot = false;
 		dbinfo[i].made_publication = false;
+
 		/* Fill subscriber attributes */
 		conninfo = concat_conninfo_dbname(sub_base_conninfo, cell->val);
 		dbinfo[i].subconninfo = conninfo;
@@ -479,9 +484,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, --enable-two-phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 dbinfo[i].two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1699,8 +1705,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  dbinfo->two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1879,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1935,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1958,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:T:U:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1995,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..e843879500 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -257,6 +257,7 @@ wal_level = logical
 max_replication_slots = 10
 max_wal_senders = 10
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 
 # Check some unmet conditions on node S
@@ -283,6 +284,7 @@ $node_s->append_conf(
 max_replication_slots = 10
 max_logical_replication_workers = 4
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 # Restore default settings on both servers
 $node_p->restart;
@@ -341,6 +343,7 @@ command_ok(
 $node_s->start;
 is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
 	't', 'standby is in recovery');
+
 $node_s->stop;
 
 # pg_createsubscriber can run without --databases option
@@ -352,7 +355,7 @@ command_ok(
 		$node_p->connstr($db1), '--socketdir',
 		$node_s->host, '--subscriber-port',
 		$node_s->port, '--replication-slot',
-		'replslot1'
+		'replslot1', '--enable-two-phase'
 	],
 	'run pg_createsubscriber without --databases');
 
@@ -387,9 +390,23 @@ is($result, qq(0),
 $node_p->safe_psql($db1, "INSERT INTO tbl1 VALUES('third row')");
 $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 
+# Prepare a transaction on the publisher
+$node_p->safe_psql(
+	$db1, qq[
+        BEGIN;
+        INSERT INTO tbl1 SELECT generate_series(1, 10);
+        PREPARE TRANSACTION 'test_prepare';
+]);
+
 # Start subscriber
 $node_s->start;
 
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(0), 'Prepared transaction replicated to subscriber');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#6Shubham Khanna
khannashubham1197@gmail.com
In reply to: Hayato Kuroda (Fujitsu) (#3)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Tue, Dec 10, 2024 at 7:42 AM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:

Dear Shubham,

Thanks for the proposal!

I am writing to propose the addition of the two_phase option in
pg_createsubscriber. As discussed in [1], supporting this feature
during the development of pg_createsubscriber was planned for this
version.

Yes, that was the pending item.

The attached patch has the changes for the same.

There are API related comments.

01.
I think the two-phase should be off by default. Because default value for CREATE
SUBSCRIPTION command is off, and your patch breaks pre-existing style.

02.
API style should be changed: no need to require the argument. I think it is enough
to provide "enable-twophase" and "T" option which specify the two_phase to true.

I have fixed the given comments. The v2 version patch attached at [1]/messages/by-id/CAHv8RjLcdmz=_RMwveuDdr8i7r=09TAwtOnFmXeaia_v2RmnYA@mail.gmail.com
has the changes for the same.
[1]: /messages/by-id/CAHv8RjLcdmz=_RMwveuDdr8i7r=09TAwtOnFmXeaia_v2RmnYA@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#7Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#4)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Tue, Dec 10, 2024 at 8:05 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Here are some review comments for the patch v1-0001.

Note -- I think Kuroda-san's idea to use a new switch like
'--enable-two-phase' would eliminate lots of my review comments below
about the inconsistencies with CREATE SUBSCRIBER, and the current
confusion about defaults and argument values etc.

======
Commit message

1.
By default, two-phase commit is enabled if the option is provided without
arguments. Users can explicitly set it to 'on' or 'off' using '--two-phase=on'
or '--two-phase=off'.

~

But you don't say what is the default if the option is omitted entirely.

~~~

2.
Notably, the replication for prepared transactions functions regardless of the
initial two-phase setting on the replication slot. However, the user cannot
change the setting after the subscription is created unless a future command,
such as 'ALTER SUBSCRIPTION ... SET (two-phase = on)', is supported.
This provides flexibility for future enhancements.

~

Typo in ALTER example with the option name /two-phase/two_phase/

~~~

3.
Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--two-phase' flag and its combinations with other options.

/flag/option/ ??

Modified the commit message according to the suggested changes.

======
doc/src/sgml/ref/pg_createsubscriber.sgml

4.
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--two_phase</option></term>
+     <listitem>
+      <para>
+       Enables or disables two-phase commit for the subscription.
+       When the option is provided without a value, it defaults to
+       <literal>on</literal>. Specify <literal>on</literal> to enable or
+       <literal>off</literal> to disable.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. By default, this option is enabled unless explicitly set
+       to <literal>off</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+

4a.
Typo -- the option you made is 'two-phase', not 'two_phase'

~

Fixed.

4b.
When you say "By default, this option is enabled unless explicitly set
to off." I take that as meaning it is default *enabled* even when the
option is entirely omitted.

But, that would be contrary to the behaviour of a normal CREATE
SUBSCRIPTION 'two_phase' option, so I wonder why should
pg_createsubscriber have a different default. Is this intentional? IMO
if no switch is specified then I thought it should be the same as the
CREATE SUBSCRIPTION default (i.e. false).

~

Modified the documentation on the basis of the latest changes added to
the patch.

4c.
This "-T" option should be moved to be adjacent (below) "-t" to keep
everything consistently in A-Z order.

Fixed.

======
src/bin/pg_basebackup/pg_createsubscriber.c

5.
bool made_replslot; /* replication slot was created */
bool made_publication; /* publication was created */
+ bool two_phase; /* two-phase option was created */

I am not sure what "option was created" even means. Cut/paste error?

Also, perhaps this field belongs with the 1st group of fields in this
struct, instead of with those made_xxx fields.

~~~

store_pub_sub_info:

6.
+ /* Store two-phase option */
+ dbinfo[i].two_phase = opt->two_phase;
+

The comment says the same as what the code is doing which seems
unhelpful/redundant. And if you rearrange the struct fields as
previously suggested, this assignment should also move.

Fixed.

7.
pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
dbinfo[i].subconninfo);
+ pg_log_debug("publisher(%d): two-phase: %s", i,
+ dbinfo[i].two_phase ? "true" : "false");

"two_phase" is a subscription option. So shouldn't this added
pg_log_debug info be part of the preceding pg_log_debug about the
subscription?

Fixed.

main:

8.
{"recovery-timeout", required_argument, NULL, 't'},
+ {"two_phase", optional_argument, NULL, 'T'},
{"subscriber-username", required_argument, NULL, 'U'},

AFAIK this option was supposed to be 'two-phase' (dash instead of
underscore is consistent with the other pg_createsubscriber options)

Fixed.

9.
opt.sub_username = NULL;
+ opt.two_phase = true;

As previously mentioned this default is not the same as the CREATE
SUBSCRIPTION default for 'two_phase', and I find that inconsistency to
be confusing.

Changed the default option to false as suggested.

10.
+ case 'T':
+ if (optarg != NULL)
+ {
+ if (strcmp(optarg, "on") == 0)
+ opt.two_phase = true;
+ else if (strcmp(optarg, "off") == 0)
+ opt.two_phase = false;
+ else
+ pg_fatal("invalid value for --two-phase: must be 'on' or 'off'");
+ }
+ else
+ opt.two_phase = true; /* Default to true if no argument
+ * is provided */
+ break;

I wonder if users familiar with the CREATE SUBSCRIPTION 'two_phase'
might find it strange that the only values accepted here are 'on' and
'off' but now all the other boolean variants.

Fixed.

======
.../t/040_pg_createsubscriber.pl

11.
+# Run pg_createsubscriber on a promoted server with two_phase=on
+command_ok(
+ [
+ 'pg_createsubscriber', '--verbose',
+ '--recovery-timeout', "$PostgreSQL::Test::Utils::timeout_default",
+ '--pgdata', $node_b->data_dir,
+ '--publisher-server', $node_a->connstr($db10),
+ '--subscriber-port', $node_b->port,
+ '--database', $db10,
+ '--two_phase=on'
+ ],
+ 'created subscription with two-phase commit enabled');

Hmm. I expect this should have been specified as '--two-phase=on'
(dash instead of underscore for consistency with all other switches of
pg_createsubscriber) but previous typos (e.g. #8 above) have
compounded the confusion.

Fixed.

The v2 version patch attached at [1]/messages/by-id/CAHv8RjLcdmz=_RMwveuDdr8i7r=09TAwtOnFmXeaia_v2RmnYA@mail.gmail.com has the changes for the same.
[1]: /messages/by-id/CAHv8RjLcdmz=_RMwveuDdr8i7r=09TAwtOnFmXeaia_v2RmnYA@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#8Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#5)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham,

Here are some review comments for patch v2-0001.

======
GENERAL - the new option name

1.
I am not sure if this new switch needed to be called
'--enable-two-phase'; probably just calling it '--two-phase' would
mean the same because specifying the switch already implies "enabling"
it to me.

Perhaps either name is fine. What do others think?

======
Commit message

2.
This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable or disable two-phase
commit for subscriptions during their creation.

~

It seems a bit strange IMO to say "enable or disable", because this is
only for "enable", and the default is disable as described in the
following sentence.

~~~

3.
By default, two-phase commit is disabled if the option is provided without
arguments.

~

But, this option now has no arguments so the part "if the option is
provided without arguments" is not applicable anymore and should be
removed. Or, if you want to say something you could say "if the option
is not provided."

======
doc/src/sgml/ref/pg_createsubscriber.sgml

4.
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables two-phase commit for the subscription. When the option is
+       provided, it is explicitly enabled. By default, two-phase commit is
+       <literal>off</literal>.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. See the
+       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       documentation for more details.
+      </para>
+     </listitem>
+    </varlistentry>
+

I felt this was more verbose than necessary. IMO you only needed to
say something very simple; the user can chase the link to learn more
about two_phase if they want to.

SUGGESTION
Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
commit for the subscription. The default is <literal>false</literal>.

======
src/bin/pg_basebackup/pg_createsubscriber.c

usage:

5.
printf(_(" -t, --recovery-timeout=SECS seconds to wait for
recovery to end\n"));
+ printf(_(" -T, --enable-two-phase enable two-phase commit for the
subscription\n"));
printf(_(" -U, --subscriber-username=NAME user name for subscriber
connection\n"));

AFAICT the patch is wrongly using tabs here instead of spaces.

~~~

store_pub_sub_info:

6.
+ dbinfo[i].two_phase = opt->two_phase; /* Set two-phase option */

I still think this comment only states the obvious so it is not
helpful. Can remove it.

~~~

7.
dbinfo[i].made_publication = false;
+
/* Fill subscriber attributes */
This whitespace change is unrelated to this patch.

~~~

8.
- pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+ pg_log_debug("subscriber(%d): subscription: %s ; connection string:
%s, --enable-two-phase: %s", i,
  dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
- dbinfo[i].subconninfo);
+ dbinfo[i].subconninfo,
+ dbinfo[i].two_phase ? "true" : "false");

IMO say "two_phase" here, not "--enable-two-phase".

======
.../t/040_pg_createsubscriber.pl

9.
max_worker_processes = 8
+max_prepared_transactions = 10
});

AFAICT the comment for this test code said:

# Restore default settings here but only apply it after testing standby. Some
# standby settings should not be a lower setting than on the primary.

But max_prepared_transactions = 10 is not the default setting for this GUC.

~~~

10.
is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
't', 'standby is in recovery');
+
$node_s->stop;

This whitespace change has nothing to do with the patch.

~~~

11.
- 'replslot1'
+ 'replslot1', '--enable-two-phase'

The comment for this test only says
# pg_createsubscriber can run without --databases option

Now it is doing more, so maybe it should give more details like "In
passing, also test the --enable-two-phase option."

~~~

12.
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(0), 'Prepared transaction replicated to subscriber');
+

Is this test OK? It says to verify it is replicated. How does checking
subscriber has zero pg_prepared_xacts ensure replication occurred?

======
Please see the attached NITPICKS diffs which includes some (not all)
of the above suggestions.

======
Kind Regards,
Peter Smith.
Fujitsu Australia

Attachments:

PS_NITPICKS_v2.txttext/plain; charset=US-ASCII; name=PS_NITPICKS_v2.txtDownload
diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 0fcd30d..d2bbef3 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -166,13 +166,8 @@ PostgreSQL documentation
      <term><option>--enable-two-phase</option></term>
      <listitem>
       <para>
-       Enables two-phase commit for the subscription. When the option is
-       provided, it is explicitly enabled. By default, two-phase commit is
-       <literal>off</literal>.
-       Two-phase commit ensures atomicity in logical replication for prepared
-       transactions. See the
-       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-       documentation for more details.
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. The default is <literal>false</literal>.
       </para>
      </listitem>
     </varlistentry>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index ce11ab7..799e49b 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -229,7 +229,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
-	printf(_("  -T, --enable-two-phase			enable two-phase commit for the subscription\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for the subscription\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -459,7 +459,7 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 		conninfo = concat_conninfo_dbname(pub_base_conninfo, cell->val);
 		dbinfo[i].pubconninfo = conninfo;
 		dbinfo[i].dbname = cell->val;
-		dbinfo[i].two_phase = opt->two_phase;	/* Set two-phase option */
+		dbinfo[i].two_phase = opt->two_phase;
 		if (num_pubs > 0)
 			dbinfo[i].pubname = pubcell->val;
 		else
@@ -484,7 +484,7 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, --enable-two-phase: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
 					 dbinfo[i].subconninfo,
 					 dbinfo[i].two_phase ? "true" : "false");
#9vignesh C
vignesh21@gmail.com
In reply to: Shubham Khanna (#5)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Tue, 10 Dec 2024 at 17:17, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Mon, Dec 9, 2024 at 7:43 PM vignesh C <vignesh21@gmail.com> wrote:

The attached Patch contains the required changes.

Few comments:
1) This is not correct, currently enabling two_phase option using
alter subscription is supported:
Notably, the replication for prepared transactions functions regardless of the
initial two-phase setting on the replication slot. However, the user cannot
change the setting after the subscription is created unless a future command,
such as 'ALTER SUBSCRIPTION ... SET (two_phase = on)', is supported.
This provides flexibility for future enhancements.

2) You can enable-two-phase on a non dry-run mode test case, as the
verification will not be possible in dry-run mode:
 # pg_createsubscriber can run without --databases option
@@ -352,7 +355,7 @@ command_ok(
                $node_p->connstr($db1), '--socketdir',
                $node_s->host, '--subscriber-port',
                $node_s->port, '--replication-slot',
-               'replslot1'
+               'replslot1', '--enable-two-phase'
3) I felt this line can be removed "Two-phase commit ensures atomicity
in logical replication for prepared transactions." as this information
is available at prepare transaction and create subscription page
documentation:
+       <literal>off</literal>.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. See the
+       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       documentation for more details.

4) This change is not required as it is not needed for the patch:
dbinfo[i].made_replslot = false;
dbinfo[i].made_publication = false;
+
/* Fill subscriber attributes */
conninfo = concat_conninfo_dbname(sub_base_conninfo, cell->val);

5) Similarly here too:
@@ -341,6 +343,7 @@ command_ok(
$node_s->start;
is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
't', 'standby is in recovery');
+
$node_s->stop;

Regards,
Vignesh

#10Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#8)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, Dec 11, 2024 at 4:21 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Here are some review comments for patch v2-0001.

======
GENERAL - the new option name

1.
I am not sure if this new switch needed to be called
'--enable-two-phase'; probably just calling it '--two-phase' would
mean the same because specifying the switch already implies "enabling"
it to me.

Perhaps either name is fine. What do others think?

======
Commit message

2.
This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable or disable two-phase
commit for subscriptions during their creation.

~

It seems a bit strange IMO to say "enable or disable", because this is
only for "enable", and the default is disable as described in the
following sentence.

~~~

3.
By default, two-phase commit is disabled if the option is provided without
arguments.

~

But, this option now has no arguments so the part "if the option is
provided without arguments" is not applicable anymore and should be
removed. Or, if you want to say something you could say "if the option
is not provided."

======
doc/src/sgml/ref/pg_createsubscriber.sgml

4.
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables two-phase commit for the subscription. When the option is
+       provided, it is explicitly enabled. By default, two-phase commit is
+       <literal>off</literal>.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. See the
+       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       documentation for more details.
+      </para>
+     </listitem>
+    </varlistentry>
+

I felt this was more verbose than necessary. IMO you only needed to
say something very simple; the user can chase the link to learn more
about two_phase if they want to.

SUGGESTION
Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
commit for the subscription. The default is <literal>false</literal>.

======
src/bin/pg_basebackup/pg_createsubscriber.c

usage:

5.
printf(_(" -t, --recovery-timeout=SECS seconds to wait for
recovery to end\n"));
+ printf(_(" -T, --enable-two-phase enable two-phase commit for the
subscription\n"));
printf(_(" -U, --subscriber-username=NAME user name for subscriber
connection\n"));

AFAICT the patch is wrongly using tabs here instead of spaces.

~~~

store_pub_sub_info:

6.
+ dbinfo[i].two_phase = opt->two_phase; /* Set two-phase option */

I still think this comment only states the obvious so it is not
helpful. Can remove it.

~~~

7.
dbinfo[i].made_publication = false;
+
/* Fill subscriber attributes */
This whitespace change is unrelated to this patch.

~~~

8.
- pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+ pg_log_debug("subscriber(%d): subscription: %s ; connection string:
%s, --enable-two-phase: %s", i,
dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
- dbinfo[i].subconninfo);
+ dbinfo[i].subconninfo,
+ dbinfo[i].two_phase ? "true" : "false");

IMO say "two_phase" here, not "--enable-two-phase".

======
.../t/040_pg_createsubscriber.pl

9.
max_worker_processes = 8
+max_prepared_transactions = 10
});

AFAICT the comment for this test code said:

# Restore default settings here but only apply it after testing standby. Some
# standby settings should not be a lower setting than on the primary.

But max_prepared_transactions = 10 is not the default setting for this GUC.

~~~

10.
is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
't', 'standby is in recovery');
+
$node_s->stop;

This whitespace change has nothing to do with the patch.

~~~

11.
- 'replslot1'
+ 'replslot1', '--enable-two-phase'

The comment for this test only says
# pg_createsubscriber can run without --databases option

Now it is doing more, so maybe it should give more details like "In
passing, also test the --enable-two-phase option."

~~~

12.
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(0), 'Prepared transaction replicated to subscriber');
+

Is this test OK? It says to verify it is replicated. How does checking
subscriber has zero pg_prepared_xacts ensure replication occurred?

======
Please see the attached NITPICKS diffs which includes some (not all)
of the above suggestions.

======

I have fixed the given comments. The attached patch contains the
suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v3-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v3-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From 453b0d90fc2f20740e583062096fd34e5c0746df Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v3] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 11 +++++++++
 src/bin/pg_basebackup/pg_createsubscriber.c   | 21 ++++++++++++----
 .../t/040_pg_createsubscriber.pl              | 24 ++++++++++++++++---
 3 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..d2bbef3567 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,17 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..01a203983d 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -53,6 +54,7 @@ struct LogicalRepInfo
 	char	   *pubname;		/* publication name */
 	char	   *subname;		/* subscription name */
 	char	   *replslotname;	/* replication slot name */
+	bool		two_phase;		/* two-phase enabled for the subscription */
 
 	bool		made_replslot;	/* replication slot was created */
 	bool		made_publication;	/* publication was created */
@@ -227,6 +229,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for the subscription\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -456,6 +459,7 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 		conninfo = concat_conninfo_dbname(pub_base_conninfo, cell->val);
 		dbinfo[i].pubconninfo = conninfo;
 		dbinfo[i].dbname = cell->val;
+		dbinfo[i].two_phase = opt->two_phase;
 		if (num_pubs > 0)
 			dbinfo[i].pubname = pubcell->val;
 		else
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 dbinfo[i].two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1699,8 +1704,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  dbinfo->two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1878,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1934,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1957,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:T:U:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1994,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..4db196052d 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -249,14 +249,16 @@ command_fails(
 		$db2
 	],
 	'primary contains unmet conditions on node P');
-# Restore default settings here but only apply it after testing standby. Some
-# standby settings should not be a lower setting than on the primary.
+# Restore default settings here (except for max_prepared_transactions as this is
+# required for --enable-two-phase) but only apply it after testing standby.
+# Some standby settings should not be a lower setting than on the primary.
 $node_p->append_conf(
 	'postgresql.conf', q{
 wal_level = logical
 max_replication_slots = 10
 max_wal_senders = 10
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 
 # Check some unmet conditions on node S
@@ -283,6 +285,7 @@ $node_s->append_conf(
 max_replication_slots = 10
 max_logical_replication_workers = 4
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 # Restore default settings on both servers
 $node_p->restart;
@@ -357,6 +360,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +375,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -387,9 +391,23 @@ is($result, qq(0),
 $node_p->safe_psql($db1, "INSERT INTO tbl1 VALUES('third row')");
 $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 
+# Prepare a transaction on the publisher
+$node_p->safe_psql(
+	$db1, qq[
+        BEGIN;
+        INSERT INTO tbl1 SELECT generate_series(1, 10);
+        PREPARE TRANSACTION 'test_prepare';
+]);
+
 # Start subscriber
 $node_s->start;
 
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(1), 'Prepared transaction replicated to subscriber');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#11Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#9)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, Dec 11, 2024 at 10:59 AM vignesh C <vignesh21@gmail.com> wrote:

On Tue, 10 Dec 2024 at 17:17, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Mon, Dec 9, 2024 at 7:43 PM vignesh C <vignesh21@gmail.com> wrote:

The attached Patch contains the required changes.

Few comments:
1) This is not correct, currently enabling two_phase option using
alter subscription is supported:
Notably, the replication for prepared transactions functions regardless of the
initial two-phase setting on the replication slot. However, the user cannot
change the setting after the subscription is created unless a future command,
such as 'ALTER SUBSCRIPTION ... SET (two_phase = on)', is supported.
This provides flexibility for future enhancements.

2) You can enable-two-phase on a non dry-run mode test case, as the
verification will not be possible in dry-run mode:
# pg_createsubscriber can run without --databases option
@@ -352,7 +355,7 @@ command_ok(
$node_p->connstr($db1), '--socketdir',
$node_s->host, '--subscriber-port',
$node_s->port, '--replication-slot',
-               'replslot1'
+               'replslot1', '--enable-two-phase'
3) I felt this line can be removed "Two-phase commit ensures atomicity
in logical replication for prepared transactions." as this information
is available at prepare transaction and create subscription page
documentation:
+       <literal>off</literal>.
+       Two-phase commit ensures atomicity in logical replication for prepared
+       transactions. See the
+       <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       documentation for more details.

4) This change is not required as it is not needed for the patch:
dbinfo[i].made_replslot = false;
dbinfo[i].made_publication = false;
+
/* Fill subscriber attributes */
conninfo = concat_conninfo_dbname(sub_base_conninfo, cell->val);

5) Similarly here too:
@@ -341,6 +343,7 @@ command_ok(
$node_s->start;
is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
't', 'standby is in recovery');
+
$node_s->stop;

I have fixed the given comments. The v3 version patch attached at [1]/messages/by-id/CAHv8RjJ8NuHLQUhkqE-fy5k+3nZUdX9QngrLaa7iS0TEJNicow@mail.gmail.com
has the changes for the same.
[1]: /messages/by-id/CAHv8RjJ8NuHLQUhkqE-fy5k+3nZUdX9QngrLaa7iS0TEJNicow@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#12vignesh C
vignesh21@gmail.com
In reply to: Shubham Khanna (#10)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, 11 Dec 2024 at 11:21, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Wed, Dec 11, 2024 at 4:21 AM Peter Smith <smithpb2250@gmail.com> wrote:

I have fixed the given comments. The attached patch contains the
suggested changes.

Since all the subscriptions are created based on the two_phase option
provided, there is no need to store this for each database:
@@ -53,6 +54,7 @@ struct LogicalRepInfo
        char       *pubname;            /* publication name */
        char       *subname;            /* subscription name */
        char       *replslotname;       /* replication slot name */
+       bool            two_phase;              /* two-phase enabled
for the subscription */

dbinfo[i].dbname = cell->val;
+ dbinfo[i].two_phase = opt->two_phase;
if (num_pubs > 0)

How about we handle something like in the attached changes.

Regards,
Vignesh

Attachments:

Remove_dbwise_two_phase.patchtext/x-patch; charset=US-ASCII; name=Remove_dbwise_two_phase.patchDownload
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index 01a203983d..d141b13bd6 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -54,7 +54,6 @@ struct LogicalRepInfo
 	char	   *pubname;		/* publication name */
 	char	   *subname;		/* subscription name */
 	char	   *replslotname;	/* replication slot name */
-	bool		two_phase;		/* two-phase enabled for the subscription */
 
 	bool		made_replslot;	/* replication slot was created */
 	bool		made_publication;	/* publication was created */
@@ -81,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -100,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -459,7 +460,6 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 		conninfo = concat_conninfo_dbname(pub_base_conninfo, cell->val);
 		dbinfo[i].pubconninfo = conninfo;
 		dbinfo[i].dbname = cell->val;
-		dbinfo[i].two_phase = opt->two_phase;
 		if (num_pubs > 0)
 			dbinfo[i].pubname = pubcell->val;
 		else
@@ -486,7 +486,7 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
 					 dbinfo[i].subconninfo,
-					 dbinfo[i].two_phase ? "true" : "false");
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1143,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1167,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1682,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1706,7 +1708,7 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 					  "WITH (create_slot = false, enabled = false, "
 					  "slot_name = %s, copy_data = false, two_phase = %s)",
 					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
-					  dbinfo->two_phase ? "true" : "false");
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -2240,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
#13Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#12)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, Dec 11, 2024 at 2:08 PM vignesh C <vignesh21@gmail.com> wrote:

On Wed, 11 Dec 2024 at 11:21, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Wed, Dec 11, 2024 at 4:21 AM Peter Smith <smithpb2250@gmail.com> wrote:

I have fixed the given comments. The attached patch contains the
suggested changes.

Since all the subscriptions are created based on the two_phase option
provided, there is no need to store this for each database:
@@ -53,6 +54,7 @@ struct LogicalRepInfo
char       *pubname;            /* publication name */
char       *subname;            /* subscription name */
char       *replslotname;       /* replication slot name */
+       bool            two_phase;              /* two-phase enabled
for the subscription */

dbinfo[i].dbname = cell->val;
+ dbinfo[i].two_phase = opt->two_phase;
if (num_pubs > 0)

How about we handle something like in the attached changes.

Thank you for pointing this out and for suggesting the changes. I
agree with your approach.
Also, I found a mistake in getopt_long and fixed it in this version of
the patch.
The attached patch contains the suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v4-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v4-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From 3f9105c7ddde30128f3f453d9484c31ff8f082b1 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v4] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 11 ++++++
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              | 24 +++++++++++--
 3 files changed, 56 insertions(+), 14 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..d2bbef3567 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,17 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..b1691d1441 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for the subscription\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..4db196052d 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -249,14 +249,16 @@ command_fails(
 		$db2
 	],
 	'primary contains unmet conditions on node P');
-# Restore default settings here but only apply it after testing standby. Some
-# standby settings should not be a lower setting than on the primary.
+# Restore default settings here (except for max_prepared_transactions as this is
+# required for --enable-two-phase) but only apply it after testing standby.
+# Some standby settings should not be a lower setting than on the primary.
 $node_p->append_conf(
 	'postgresql.conf', q{
 wal_level = logical
 max_replication_slots = 10
 max_wal_senders = 10
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 
 # Check some unmet conditions on node S
@@ -283,6 +285,7 @@ $node_s->append_conf(
 max_replication_slots = 10
 max_logical_replication_workers = 4
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 # Restore default settings on both servers
 $node_p->restart;
@@ -357,6 +360,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +375,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -387,9 +391,23 @@ is($result, qq(0),
 $node_p->safe_psql($db1, "INSERT INTO tbl1 VALUES('third row')");
 $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 
+# Prepare a transaction on the publisher
+$node_p->safe_psql(
+	$db1, qq[
+        BEGIN;
+        INSERT INTO tbl1 SELECT generate_series(1, 10);
+        PREPARE TRANSACTION 'test_prepare';
+]);
+
 # Start subscriber
 $node_s->start;
 
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(1), 'Prepared transaction replicated to subscriber');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#14Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#13)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham,

Here are some review comments for the patch v4-0001.

======
GENERAL.

1.
After reading Vignesh's last review and then the pg_createsubscriber
documentation I see there can be multiple databases simultaneously
specified (by writing multiple -d switches) and in that case each one
gets its own:
--publication
--replication-slot
--subscription

OTOH, this new '--enable-two-phase' switch is just a blanket two_phase
enablement across all subscriptions. There is no way for the user to
say if they want it enabled for some subscriptions (on some databases)
but not for others. I suppose this was intentional and OK (right?),
but this point needs to be clarified in the docs.

======
doc/src/sgml/ref/pg_createsubscriber.sgml

2.
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. The default is <literal>false</literal>.
+      </para>

Following on from the previous review comment. Since this switch is
applied to all database subscriptions I think the help needs to say
that. Something like.

"If there are multiple subscriptions specified, this option applies to
all of them."

~~~

3.
In the "Prerequisites" sections of the docs, it gives requirements for
various GUC settings on the source server and the target server. So,
should there be another note here advising to configure the
'max_prepared_transactions' GUC when the '--enable-two-phase' is
specified?

~~~

4. "Warnings" section includes the following:

pg_createsubscriber sets up logical replication with two-phase commit
disabled. This means that any prepared transactions will be replicated
at the time of COMMIT PREPARED, without advance preparation. Once
setup is complete, you can manually drop and re-create the
subscription(s) with the two_phase option enabled.

~

The above text is from the "Warnings" section, but it seems stale
information that needs to be updated due to the introduction of this
new '--enable-two-phase' option.

======
src/bin/pg_basebackup/pg_createsubscriber.c

usage:
5.
printf(_(" -t, --recovery-timeout=SECS seconds to wait for
recovery to end\n"));
+ printf(_(" -T, --enable-two-phase enable two-phase commit
for the subscription\n"));

Given the previous review comments (#1/#2 etc), I was wondering if it
might be better to say more like "enable two-phase commit for all
subscriptions".

======
.../t/040_pg_createsubscriber.pl

6.
+is($count_prepared_s, qq(1), 'Prepared transaction replicated to subscriber');

Should there also have been an earlier check (way back before the
PREPARE) just to make sure this count was initially 0?

======
Kind Regards,
Peter Smith.
Fujitsu Australia

#15Hayato Kuroda (Fujitsu)
kuroda.hayato@fujitsu.com
In reply to: Shubham Khanna (#13)
RE: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Dear Shubham,

Thank you for pointing this out and for suggesting the changes. I
agree with your approach.
Also, I found a mistake in getopt_long and fixed it in this version of
the patch.
The attached patch contains the suggested changes.

Thanks for updating the patch. I think the patch looks mostly OK.

Regarding the test code - I think we should directly refer the pg_subscription catalog,
and confirm that subtwophase is 'p'. IIUC, we can wait until
all subscriptions. Subtwophasestate are 'e', by using poll_query_until() and [1]SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');.

[1]: SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

Best regards,
Hayato Kuroda
FUJITSU LIMITED

#16vignesh C
vignesh21@gmail.com
In reply to: Hayato Kuroda (Fujitsu) (#15)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Thu, 12 Dec 2024 at 08:14, Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:

Dear Shubham,

Thank you for pointing this out and for suggesting the changes. I
agree with your approach.
Also, I found a mistake in getopt_long and fixed it in this version of
the patch.
The attached patch contains the suggested changes.

Thanks for updating the patch. I think the patch looks mostly OK.

Regarding the test code - I think we should directly refer the pg_subscription catalog,
and confirm that subtwophase is 'p'. IIUC, we can wait until
all subscriptions. Subtwophasestate are 'e', by using poll_query_until() and [1].

[1]: SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

Yes, that approach is better. Also this is not required after checking
using pg_subscription:
+# Prepare a transaction on the publisher
+$node_p->safe_psql(
+ $db1, qq[
+        BEGIN;
+        INSERT INTO tbl1 SELECT generate_series(1, 10);
+        PREPARE TRANSACTION 'test_prepare';
+]);
+
 # Start subscriber
 $node_s->start;
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(1), 'Prepared transaction replicated to subscriber');

Regards,
Vignesh

#17Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#14)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Thu, Dec 12, 2024 at 6:04 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Here are some review comments for the patch v4-0001.

======
GENERAL.

1.
After reading Vignesh's last review and then the pg_createsubscriber
documentation I see there can be multiple databases simultaneously
specified (by writing multiple -d switches) and in that case each one
gets its own:
--publication
--replication-slot
--subscription

OTOH, this new '--enable-two-phase' switch is just a blanket two_phase
enablement across all subscriptions. There is no way for the user to
say if they want it enabled for some subscriptions (on some databases)
but not for others. I suppose this was intentional and OK (right?),
but this point needs to be clarified in the docs.

Fixed.

======
doc/src/sgml/ref/pg_createsubscriber.sgml

2.
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. The default is <literal>false</literal>.
+      </para>

Following on from the previous review comment. Since this switch is
applied to all database subscriptions I think the help needs to say
that. Something like.

"If there are multiple subscriptions specified, this option applies to
all of them."

~~~

Fixed.

3.
In the "Prerequisites" sections of the docs, it gives requirements for
various GUC settings on the source server and the target server. So,
should there be another note here advising to configure the
'max_prepared_transactions' GUC when the '--enable-two-phase' is
specified?

~~~

Fixed.

4. "Warnings" section includes the following:

pg_createsubscriber sets up logical replication with two-phase commit
disabled. This means that any prepared transactions will be replicated
at the time of COMMIT PREPARED, without advance preparation. Once
setup is complete, you can manually drop and re-create the
subscription(s) with the two_phase option enabled.

~

The above text is from the "Warnings" section, but it seems stale
information that needs to be updated due to the introduction of this
new '--enable-two-phase' option.

Fixed.

======
src/bin/pg_basebackup/pg_createsubscriber.c

usage:
5.
printf(_(" -t, --recovery-timeout=SECS seconds to wait for
recovery to end\n"));
+ printf(_(" -T, --enable-two-phase enable two-phase commit
for the subscription\n"));

Given the previous review comments (#1/#2 etc), I was wondering if it
might be better to say more like "enable two-phase commit for all
subscriptions".

Fixed.

======
.../t/040_pg_createsubscriber.pl

6.
+is($count_prepared_s, qq(1), 'Prepared transaction replicated to subscriber');

Should there also have been an earlier check (way back before the
PREPARE) just to make sure this count was initially 0?

Removed this and added a new test case instead of this.

The attached patch contains the suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v5-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v5-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From 9d35e48c64911880ec7c1fb3c7e3fdc47c923854 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v5] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 30 +++++++++++-----
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              | 24 +++++++++++--
 3 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..d92b43e7ae 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -296,7 +309,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    --enable-two-phase switch then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -356,14 +371,13 @@ PostgreSQL documentation
    </para>
 
    <para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If --enable-two-phase switch is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with
     the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.
    </para>
 
    <para>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..f5d9fbc2e8 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..488841e09f 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -249,14 +249,16 @@ command_fails(
 		$db2
 	],
 	'primary contains unmet conditions on node P');
-# Restore default settings here but only apply it after testing standby. Some
-# standby settings should not be a lower setting than on the primary.
+# Restore default settings here (except for max_prepared_transactions as this is
+# required for --enable-two-phase) but only apply it after testing standby.
+# Some standby settings should not be a lower setting than on the primary.
 $node_p->append_conf(
 	'postgresql.conf', q{
 wal_level = logical
 max_replication_slots = 10
 max_wal_senders = 10
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 
 # Check some unmet conditions on node S
@@ -283,6 +285,7 @@ $node_s->append_conf(
 max_replication_slots = 10
 max_logical_replication_workers = 4
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 # Restore default settings on both servers
 $node_p->restart;
@@ -357,6 +360,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,10 +375,24 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
+# Start subscriber
+$node_s->start;
+
+# Verify that the subtwophase is 'p' in the pg_subscription catalog
+my $poll_query_until = $node_s->safe_psql('postgres',
+	"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');"
+);
+
+is($poll_query_until, qq(t),
+	'Timed out while waiting for subscriber to enable twophase');
+
+# Stop subscriber
+$node_s->stop;
+
 # Confirm the physical replication slot has been removed
 $result = $node_p->safe_psql($db1,
 	"SELECT count(*) FROM pg_replication_slots WHERE slot_name = '$slotname'"
-- 
2.34.1

#18Shubham Khanna
khannashubham1197@gmail.com
In reply to: Hayato Kuroda (Fujitsu) (#15)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Thu, Dec 12, 2024 at 8:14 AM Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:

Dear Shubham,

Thank you for pointing this out and for suggesting the changes. I
agree with your approach.
Also, I found a mistake in getopt_long and fixed it in this version of
the patch.
The attached patch contains the suggested changes.

Thanks for updating the patch. I think the patch looks mostly OK.

Regarding the test code - I think we should directly refer the pg_subscription catalog,
and confirm that subtwophase is 'p'. IIUC, we can wait until
all subscriptions. Subtwophasestate are 'e', by using poll_query_until() and [1].

[1]: SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

I have fixed the given comment. The v5 version patch attached at [1]/messages/by-id/CAHv8Rj+hd2MTNRs4AsK6=hRqvV6JC9g2tTAJwGjrNfXg6vhD8g@mail.gmail.com
has the changes for the same.
[1]: /messages/by-id/CAHv8Rj+hd2MTNRs4AsK6=hRqvV6JC9g2tTAJwGjrNfXg6vhD8g@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#19Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#16)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Thu, Dec 12, 2024 at 9:34 AM vignesh C <vignesh21@gmail.com> wrote:

On Thu, 12 Dec 2024 at 08:14, Hayato Kuroda (Fujitsu)
<kuroda.hayato@fujitsu.com> wrote:

Dear Shubham,

Thank you for pointing this out and for suggesting the changes. I
agree with your approach.
Also, I found a mistake in getopt_long and fixed it in this version of
the patch.
The attached patch contains the suggested changes.

Thanks for updating the patch. I think the patch looks mostly OK.

Regarding the test code - I think we should directly refer the pg_subscription catalog,
and confirm that subtwophase is 'p'. IIUC, we can wait until
all subscriptions. Subtwophasestate are 'e', by using poll_query_until() and [1].

[1]: SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

Yes, that approach is better. Also this is not required after checking
using pg_subscription:
+# Prepare a transaction on the publisher
+$node_p->safe_psql(
+ $db1, qq[
+        BEGIN;
+        INSERT INTO tbl1 SELECT generate_series(1, 10);
+        PREPARE TRANSACTION 'test_prepare';
+]);
+
# Start subscriber
$node_s->start;
+# Verify that the prepared transaction is replicated to the subscriber
+my $count_prepared_s =
+  $node_s->safe_psql($db1, "SELECT count(*) FROM pg_prepared_xacts;");
+
+is($count_prepared_s, qq(1), 'Prepared transaction replicated to subscriber');

I have fixed the given comment. The v5 version patch attached at [1]/messages/by-id/CAHv8Rj+hd2MTNRs4AsK6=hRqvV6JC9g2tTAJwGjrNfXg6vhD8g@mail.gmail.com
has the changes for the same.
[1]: /messages/by-id/CAHv8Rj+hd2MTNRs4AsK6=hRqvV6JC9g2tTAJwGjrNfXg6vhD8g@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#20Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#17)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham,

Here are my review comments for the patch v5-0001.

======
doc/src/sgml/ref/pg_createsubscriber.sgml

1.
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    --enable-two-phase switch then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.

Should use sgml <option> markup for "--enable-two-phase".

~~~

2.
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If --enable-two-phase switch is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with

Should use sgml <option> markup for "--enable-two-phase".

======
.../t/040_pg_createsubscriber.pl

3.
+# Verify that the subtwophase is 'p' in the pg_subscription catalog
+my $poll_query_until = $node_s->safe_psql('postgres',
+ "SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT
IN ('e');"
+);
+
+is($poll_query_until, qq(t),
+ 'Timed out while waiting for subscriber to enable twophase');

3a.
Hmm. Does this code match the comment? The comment says verify
subtwophase is 'p' (aka "pending") but AFAICT the code is actually
waiting until every subtwophase is 'e' (aka "enabled").

~

3b.
Also, if you are going to name char-codes (like 'p') in comments, it
might be helpful to include the equivalent words, saving readers from
having to search the documentation to find the meaning.

======
Kind Regards,
Peter Smith.
Fujitsu Australia

#21Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#20)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Dec 13, 2024 at 4:42 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Here are my review comments for the patch v5-0001.

======
doc/src/sgml/ref/pg_createsubscriber.sgml

1.
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    --enable-two-phase switch then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.

Should use sgml <option> markup for "--enable-two-phase".

~~~

2.
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If --enable-two-phase switch is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with

Should use sgml <option> markup for "--enable-two-phase".

======
.../t/040_pg_createsubscriber.pl

3.
+# Verify that the subtwophase is 'p' in the pg_subscription catalog
+my $poll_query_until = $node_s->safe_psql('postgres',
+ "SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT
IN ('e');"
+);
+
+is($poll_query_until, qq(t),
+ 'Timed out while waiting for subscriber to enable twophase');

3a.
Hmm. Does this code match the comment? The comment says verify
subtwophase is 'p' (aka "pending") but AFAICT the code is actually
waiting until every subtwophase is 'e' (aka "enabled").

~

3b.
Also, if you are going to name char-codes (like 'p') in comments, it
might be helpful to include the equivalent words, saving readers from
having to search the documentation to find the meaning.

I have fixed the given comments. The attached patch contains the
suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v6-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v6-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From 3a9cd96aa8d2c1cdb5ded0d447097728fb910ede Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v6] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 30 +++++++++++-----
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              | 21 +++++++++--
 3 files changed, 64 insertions(+), 22 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..7cae5cbe70 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -296,7 +309,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -356,14 +371,13 @@ PostgreSQL documentation
    </para>
 
    <para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If <option>--enable-two-phase</option> is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with
     the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.
    </para>
 
    <para>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..f5d9fbc2e8 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..8001cbb644 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -249,14 +249,16 @@ command_fails(
 		$db2
 	],
 	'primary contains unmet conditions on node P');
-# Restore default settings here but only apply it after testing standby. Some
-# standby settings should not be a lower setting than on the primary.
+# Restore default settings here (except for max_prepared_transactions as this is
+# required for --enable-two-phase) but only apply it after testing standby.
+# Some standby settings should not be a lower setting than on the primary.
 $node_p->append_conf(
 	'postgresql.conf', q{
 wal_level = logical
 max_replication_slots = 10
 max_wal_senders = 10
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 
 # Check some unmet conditions on node S
@@ -283,6 +285,7 @@ $node_s->append_conf(
 max_replication_slots = 10
 max_logical_replication_workers = 4
 max_worker_processes = 8
+max_prepared_transactions = 10
 });
 # Restore default settings on both servers
 $node_p->restart;
@@ -357,6 +360,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,10 +375,21 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
+# Start subscriber
+$node_s->start;
+
+# Verify that the subtwophase is enabled ('e') in the pg_subscription catalog
+$node_s->poll_query_until('postgres',
+	"SELECT count(1) = 2 FROM pg_subscription WHERE subtwophasestate = 'e';")
+  or die "Timed out while waiting for subscriber to enable twophase";
+
+# Stop subscriber
+$node_s->stop;
+
 # Confirm the physical replication slot has been removed
 $result = $node_p->safe_psql($db1,
 	"SELECT count(*) FROM pg_replication_slots WHERE slot_name = '$slotname'"
-- 
2.41.0.windows.3

#22Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#21)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham.

Here are my review comments for v6-0001.

======
1.
+# Verify that the subtwophase is enabled ('e') in the pg_subscription catalog
+$node_s->poll_query_until('postgres',
+ "SELECT count(1) = 2 FROM pg_subscription WHERE subtwophasestate = 'e';")
+  or die "Timed out while waiting for subscriber to enable twophase";
+

This form of the SQL is probably OK but it's a bit tricky; Probably it
should have been explained in the comment about where that count "2"
has come from.

~~

I think it was OK as before (v5) to be querying until nothing was NOT
'e'. In other words, until everything was enabled 'e'.
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

~~

OTOH, to save execution time we really would be satisfied with both
'p' and 'e' states here. (we don't strictly need to wait for the
transition from 'p' to 'e' to occur).

So, SQL like the one below might be the best:

# Verify that all subtwophase states are pending or enabled,
# e.g. there are no subscriptions where subtwophase is disabled ('d').
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate IN ('d')

======
Kind Regards,
Peter Smith.
Fujitsu Australia

#23Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#22)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Dec 13, 2024 at 12:20 PM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham.

Here are my review comments for v6-0001.

======
1.
+# Verify that the subtwophase is enabled ('e') in the pg_subscription catalog
+$node_s->poll_query_until('postgres',
+ "SELECT count(1) = 2 FROM pg_subscription WHERE subtwophasestate = 'e';")
+  or die "Timed out while waiting for subscriber to enable twophase";
+

This form of the SQL is probably OK but it's a bit tricky; Probably it
should have been explained in the comment about where that count "2"
has come from.

~~

I think it was OK as before (v5) to be querying until nothing was NOT
'e'. In other words, until everything was enabled 'e'.
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

~~

OTOH, to save execution time we really would be satisfied with both
'p' and 'e' states here. (we don't strictly need to wait for the
transition from 'p' to 'e' to occur).

So, SQL like the one below might be the best:

# Verify that all subtwophase states are pending or enabled,
# e.g. there are no subscriptions where subtwophase is disabled ('d').
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate IN ('d')

I have fixed the given comment. Since prepared transactions are not
being used anymore, I have removed it from the test file.
The attached patch contains the suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v7-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v7-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From 7a37e711a1dd3a782ba7b7486e7d5845e7ccc61b Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v7] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.

Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 30 +++++++++++-----
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              |  9 ++++-
 3 files changed, 54 insertions(+), 20 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..7cae5cbe70 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -296,7 +309,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -356,14 +371,13 @@ PostgreSQL documentation
    </para>
 
    <para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If <option>--enable-two-phase</option> is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with
     the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.
    </para>
 
    <para>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..f5d9fbc2e8 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..b4c2156c5b 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,12 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+$node_s->poll_query_until('postgres',
+	"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate IN ('d');"
+);
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.34.1

#24vignesh C
vignesh21@gmail.com
In reply to: Shubham Khanna (#23)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, 13 Dec 2024 at 13:01, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 13, 2024 at 12:20 PM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham.

Here are my review comments for v6-0001.

======
1.
+# Verify that the subtwophase is enabled ('e') in the pg_subscription catalog
+$node_s->poll_query_until('postgres',
+ "SELECT count(1) = 2 FROM pg_subscription WHERE subtwophasestate = 'e';")
+  or die "Timed out while waiting for subscriber to enable twophase";
+

This form of the SQL is probably OK but it's a bit tricky; Probably it
should have been explained in the comment about where that count "2"
has come from.

~~

I think it was OK as before (v5) to be querying until nothing was NOT
'e'. In other words, until everything was enabled 'e'.
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

~~

OTOH, to save execution time we really would be satisfied with both
'p' and 'e' states here. (we don't strictly need to wait for the
transition from 'p' to 'e' to occur).

So, SQL like the one below might be the best:

# Verify that all subtwophase states are pending or enabled,
# e.g. there are no subscriptions where subtwophase is disabled ('d').
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate IN ('d')

I have fixed the given comment. Since prepared transactions are not
being used anymore, I have removed it from the test file.
The attached patch contains the suggested changes.

Few comments:
1) Since we are providing --enable-two-phase option now and user can
create subscriptions with two-phase option this warning can be removed
now:
    <para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If <option>--enable-two-phase</option> is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with
     the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.

2) Since we are not going to wait till the subscriptions are enabled,
we can use safe_psql instead of poll_query_until, something like:
is($node_s->safe_psql('postgres',
"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"),
't', 'subscriptions are created with the two-phase option enabled');

instead of:
+$node_s->poll_query_until('postgres',
+       "SELECT count(1) = 0 FROM pg_subscription WHERE
subtwophasestate IN ('d');"
+);

3) This can be removed from the commit message:
Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.

4) Should" two-phase option" be "enable-two-phase option" here:
const char *sub_username; /* subscriber username */
+ bool two_phase; /* two-phase option */
SimpleStringList database_names; /* list of database names */

Regards,
Vignesh

#25Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#24)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Dec 13, 2024 at 2:39 PM vignesh C <vignesh21@gmail.com> wrote:

On Fri, 13 Dec 2024 at 13:01, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 13, 2024 at 12:20 PM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham.

Here are my review comments for v6-0001.

======
1.
+# Verify that the subtwophase is enabled ('e') in the pg_subscription catalog
+$node_s->poll_query_until('postgres',
+ "SELECT count(1) = 2 FROM pg_subscription WHERE subtwophasestate = 'e';")
+  or die "Timed out while waiting for subscriber to enable twophase";
+

This form of the SQL is probably OK but it's a bit tricky; Probably it
should have been explained in the comment about where that count "2"
has come from.

~~

I think it was OK as before (v5) to be querying until nothing was NOT
'e'. In other words, until everything was enabled 'e'.
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

~~

OTOH, to save execution time we really would be satisfied with both
'p' and 'e' states here. (we don't strictly need to wait for the
transition from 'p' to 'e' to occur).

So, SQL like the one below might be the best:

# Verify that all subtwophase states are pending or enabled,
# e.g. there are no subscriptions where subtwophase is disabled ('d').
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate IN ('d')

I have fixed the given comment. Since prepared transactions are not
being used anymore, I have removed it from the test file.
The attached patch contains the suggested changes.

Few comments:
1) Since we are providing --enable-two-phase option now and user can
create subscriptions with two-phase option this warning can be removed
now:
<para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If <option>--enable-two-phase</option> is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with
the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.

2) Since we are not going to wait till the subscriptions are enabled,
we can use safe_psql instead of poll_query_until, something like:
is($node_s->safe_psql('postgres',
"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"),
't', 'subscriptions are created with the two-phase option enabled');

instead of:
+$node_s->poll_query_until('postgres',
+       "SELECT count(1) = 0 FROM pg_subscription WHERE
subtwophasestate IN ('d');"
+);

3) This can be removed from the commit message:
Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.

4) Should" two-phase option" be "enable-two-phase option" here:
const char *sub_username; /* subscriber username */
+ bool two_phase; /* two-phase option */
SimpleStringList database_names; /* list of database names */

I have fixed the given comments. The attached patch contains the
suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v8-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v8-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From e4a75d58c11834ed7595e00807d4bd4f530814da Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v8] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 17 ++++++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              | 12 ++++++-
 3 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index df1a92b4da..f8a39113f5 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -161,6 +161,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -296,7 +309,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..42d5c1ddc0 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..0043f0ca6e 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.34.1

#26Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#25)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.
#27vignesh C
vignesh21@gmail.com
In reply to: Shubham Khanna (#25)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, 13 Dec 2024 at 15:33, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 13, 2024 at 2:39 PM vignesh C <vignesh21@gmail.com> wrote:

On Fri, 13 Dec 2024 at 13:01, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 13, 2024 at 12:20 PM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham.

Here are my review comments for v6-0001.

======
1.
+# Verify that the subtwophase is enabled ('e') in the pg_subscription catalog
+$node_s->poll_query_until('postgres',
+ "SELECT count(1) = 2 FROM pg_subscription WHERE subtwophasestate = 'e';")
+  or die "Timed out while waiting for subscriber to enable twophase";
+

This form of the SQL is probably OK but it's a bit tricky; Probably it
should have been explained in the comment about where that count "2"
has come from.

~~

I think it was OK as before (v5) to be querying until nothing was NOT
'e'. In other words, until everything was enabled 'e'.
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate NOT IN ('e');

~~

OTOH, to save execution time we really would be satisfied with both
'p' and 'e' states here. (we don't strictly need to wait for the
transition from 'p' to 'e' to occur).

So, SQL like the one below might be the best:

# Verify that all subtwophase states are pending or enabled,
# e.g. there are no subscriptions where subtwophase is disabled ('d').
SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate IN ('d')

I have fixed the given comment. Since prepared transactions are not
being used anymore, I have removed it from the test file.
The attached patch contains the suggested changes.

Few comments:
1) Since we are providing --enable-two-phase option now and user can
create subscriptions with two-phase option this warning can be removed
now:
<para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
+    If <option>--enable-two-phase</option> is not specified, the
+    <application>pg_createsubscriber</application> sets up logical replication
+    with two-phase commit disabled.  This means that any prepared transactions
+    will be replicated at the time of <command>COMMIT PREPARED</command>,
+    without advance preparation. Once setup is complete, you can manually drop
+    and re-create the subscription(s) with
the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.

2) Since we are not going to wait till the subscriptions are enabled,
we can use safe_psql instead of poll_query_until, something like:
is($node_s->safe_psql('postgres',
"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"),
't', 'subscriptions are created with the two-phase option enabled');

instead of:
+$node_s->poll_query_until('postgres',
+       "SELECT count(1) = 0 FROM pg_subscription WHERE
subtwophasestate IN ('d');"
+);

3) This can be removed from the commit message:
Documentation has been updated to reflect the new option, and test cases have
been added to validate various scenarios, including proper validation of the
'--enable-two-phase' option and its combinations with other options.

4) Should" two-phase option" be "enable-two-phase option" here:
const char *sub_username; /* subscriber username */
+ bool two_phase; /* two-phase option */
SimpleStringList database_names; /* list of database names */

I have fixed the given comments. The attached patch contains the
suggested changes.

The documentation requires a minor update: instead of specifying
subscriptions, the user will specify multiple databases, and the
subscription will be created on the specified databases. Documentation
should be updated accordingly:
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.

Regards,
Vignesh

#28Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#27)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Dec 27, 2024 at 11:30 AM vignesh C <vignesh21@gmail.com> wrote:

The documentation requires a minor update: instead of specifying
subscriptions, the user will specify multiple databases, and the
subscription will be created on the specified databases. Documentation
should be updated accordingly:
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.

I have fixed the given comment. The attached patch contains the
suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v9-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchapplication/octet-stream; name=v9-0001-Add-support-for-two-phase-commit-in-pg_createsubs.patchDownload
From 6006c15e65e06f3cec3acdd7afebf37ba19b092a Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v9] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 17 ++++++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              | 12 ++++++-
 3 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e..54818f472b 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index e96370a9ec..42d5c1ddc0 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 0a900edb65..0043f0ca6e 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#29Ajin Cherian
itsajin@gmail.com
In reply to: Shubham Khanna (#28)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Dec 27, 2024 at 5:36 PM Shubham Khanna
<khannashubham1197@gmail.com> wrote:

The patch no longer applies on HEAD. Please do rebase.

regards,
Ajin Cherian
Fujitsu Australia

#30Ajin Cherian
itsajin@gmail.com
In reply to: Ajin Cherian (#29)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Jan 10, 2025 at 9:08 PM Ajin Cherian <itsajin@gmail.com> wrote:

On Fri, Dec 27, 2024 at 5:36 PM Shubham Khanna
<khannashubham1197@gmail.com> wrote:

The patch no longer applies on HEAD. Please do rebase.

Sorry, I was mistaken. Ignore this. The patch does apply on HEAD.

regards,
Ajin Cherian

#31Shlok Kyal
shlok.kyal.oss@gmail.com
In reply to: Shubham Khanna (#28)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, 27 Dec 2024 at 12:06, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 27, 2024 at 11:30 AM vignesh C <vignesh21@gmail.com> wrote:

The documentation requires a minor update: instead of specifying
subscriptions, the user will specify multiple databases, and the
subscription will be created on the specified databases. Documentation
should be updated accordingly:
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.

I have fixed the given comment. The attached patch contains the
suggested changes.

Hi Shubham,

I have reviewed the v9 patch. It looks fine to me. I have tested it
and it is working as expected.
I have few comments:

1.
-       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s", i,
+       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s, two_phase: %s", i,
                     dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-                    dbinfo[i].subconninfo);
+                    dbinfo[i].subconninfo,
+                    opt->two_phase ? "true" : "false");
Here the value of 'opt->two_phase' will be the same for all
subscriptions. So is it good to log it here? Thoughts?

2. In documentation pg_createsubscriber.sgml. Under Warning section,
we have following:

<application>pg_createsubscriber</application> sets up logical
replication with two-phase commit disabled. This means that any
prepared transactions will be replicated at the time
of <command>COMMIT PREPARED</command>, without advance preparation.
Once setup is complete, you can manually drop and re-create the
subscription(s) with
the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
option enabled.

I think we should update the documentation accordingly.

Thanks and Regards,
Shlok Kyal

#32Shubham Khanna
khannashubham1197@gmail.com
In reply to: Shlok Kyal (#31)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Tue, Jan 14, 2025 at 4:53 PM Shlok Kyal <shlok.kyal.oss@gmail.com> wrote:

On Fri, 27 Dec 2024 at 12:06, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 27, 2024 at 11:30 AM vignesh C <vignesh21@gmail.com> wrote:

The documentation requires a minor update: instead of specifying
subscriptions, the user will specify multiple databases, and the
subscription will be created on the specified databases. Documentation
should be updated accordingly:
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.

I have fixed the given comment. The attached patch contains the
suggested changes.

Hi Shubham,

I have reviewed the v9 patch. It looks fine to me. I have tested it
and it is working as expected.
I have few comments:

1.
-       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s", i,
+       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s, two_phase: %s", i,
dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-                    dbinfo[i].subconninfo);
+                    dbinfo[i].subconninfo,
+                    opt->two_phase ? "true" : "false");
Here the value of 'opt->two_phase' will be the same for all
subscriptions. So is it good to log it here? Thoughts?

Here, the 'two-phase option' is a global setting, so it makes sense to
log it consistently within the debug output. While it is true that
'opt->two_phase' will have the same value for all subscriptions,
including it in the debug log provides clarity about the replication
setup at the time of execution.
This information can be particularly useful for debugging purposes, as
it gives immediate insight into whether a 'two-phase' commit was
enabled or disabled during the setup. Therefore, logging the
'two-phase' status, even though it's consistent across subscriptions,
adds valuable context to the debug output.

2. In documentation pg_createsubscriber.sgml. Under Warning section,
we have following:

<application>pg_createsubscriber</application> sets up logical
replication with two-phase commit disabled. This means that any
prepared transactions will be replicated at the time
of <command>COMMIT PREPARED</command>, without advance preparation.
Once setup is complete, you can manually drop and re-create the
subscription(s) with
the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
option enabled.

I think we should update the documentation accordingly.

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

The attached patch contains the required changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v10-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v10-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From 6f77b368ba3d2626838640c934e126ac58b10692 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v10] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 28 ++++++++-------
 src/bin/pg_basebackup/pg_createsubscriber.c   | 35 +++++++++++++------
 .../t/040_pg_createsubscriber.pl              | 12 ++++++-
 3 files changed, 51 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e..c9f33d4d3e 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -359,17 +374,6 @@ PostgreSQL documentation
     <application>pg_createsubscriber</application>.
    </para>
 
-   <para>
-    <application>pg_createsubscriber</application> sets up logical
-    replication with two-phase commit disabled.  This means that any
-    prepared transactions will be replicated at the time
-    of <command>COMMIT PREPARED</command>, without advance preparation.
-    Once setup is complete, you can manually drop and re-create the
-    subscription(s) with
-    the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
-    option enabled.
-   </para>
-
    <para>
     <application>pg_createsubscriber</application> changes the system
     identifier using <application>pg_resetwal</application>.  It would avoid
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index faf18ccf13..86bd798a55 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -79,7 +80,7 @@ static void check_publisher(const struct LogicalRepInfo *dbinfo);
 static char *setup_publisher(struct LogicalRepInfo *dbinfo);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
@@ -98,7 +99,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +230,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +483,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -1138,7 +1143,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1168,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1677,7 +1683,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1706,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1880,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1936,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1959,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +1996,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2229,7 +2242,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 5426159fa5..bd9a4d5032 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#33Ajin Cherian
itsajin@gmail.com
In reply to: Shubham Khanna (#32)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, Jan 15, 2025 at 5:33 PM Shubham Khanna <khannashubham1197@gmail.com>
wrote:

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

Hi Shubham,

Even though the documentation is updated, the actual code still gives a
warning, when you try and create pg_createsubscriber with the
--enable-two-phase option:

pg_createsubscriber: warning: two_phase option will not be enabled for
replication slots
pg_createsubscriber: detail: Subscriptions will be created with the
two_phase option disabled. Prepared transactions will be replicated at
COMMIT PREPARED.

This is coming from code in check_publisher()

if (max_prepared_transactions != 0)
{
pg_log_warning("two_phase option will not be enabled for
replication slots");
pg_log_warning_detail("Subscriptions will be created with the
two_phase option disabled. "
"Prepared transactions will be replicated at
COMMIT PREPARED.");
}

I think this code needs to be updated as well.

regards,
Ajin Cherian
Fujitsu Australia

#34Peter Smith
smithpb2250@gmail.com
In reply to: Ajin Cherian (#33)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, Jan 15, 2025 at 9:24 PM Ajin Cherian <itsajin@gmail.com> wrote:

On Wed, Jan 15, 2025 at 5:33 PM Shubham Khanna <khannashubham1197@gmail.com> wrote:

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

Hi Shubham,

Even though the documentation is updated, the actual code still gives a warning, when you try and create pg_createsubscriber with the --enable-two-phase option:

pg_createsubscriber: warning: two_phase option will not be enabled for replication slots
pg_createsubscriber: detail: Subscriptions will be created with the two_phase option disabled. Prepared transactions will be replicated at COMMIT PREPARED.

This is coming from code in check_publisher()

if (max_prepared_transactions != 0)
{
pg_log_warning("two_phase option will not be enabled for replication slots");
pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled. "
"Prepared transactions will be replicated at COMMIT PREPARED.");
}

I think this code needs to be updated as well.

Hi Shubham.

I'm not so sure about the documentation change of v10.

Sure, we can remove the warnings in the docs and just assume/expect
the user to be aware that there is a new '--enable-two-phase' option
that they should be using. That is what v10 is now doing.

OTOH, if the user (accidentally?) omits the new '--enable-two-phase'
switch then all this two-phase documentation still seems relevant.

~

In other words, we could've left all this documented information
intact, but just qualify the paragraph. For example:

CURRENTLY (v9)
pg_createsubscriber sets up logical replication with two-phase commit
disabled. This means that any prepared transactions will be replicated
at the time of COMMIT PREPARED, without advance preparation. Once
setup is complete, you can manually drop and re-create the
subscription(s) with the two_phase option enabled.

SUGGESTION
Unless the '--enable-two-phase' switch is specified,
pg_createsubscriber sets up ...

~

Similarly, to help (accident prone?) users we could leave this code
warning [1]/messages/by-id/CAFPTHDbdXa4wh01B98L8VssJnyH=uK6Qfi=sKKVXRq-9_YwXsg@mail.gmail.com intact, but just change the condition slightly, and add a
helpful hint on how to avoid the problem.

CURRENTLY (v10)
if (max_prepared_transactions != 0)
{
pg_log_warning("two_phase option will not be enabled for
replication slots");
pg_log_warning_detail("Subscriptions will be created with the
two_phase option disabled. "
"Prepared transactions will be
replicated at COMMIT PREPARED.");
}

SUGGESTION
if (max_prepared_transactions != 0 && !opt->two_phase)
{
pg_log_warning("two_phase option will not be enabled for
replication slots");
pg_log_warning_detail("Subscriptions will be created with the
two_phase option disabled. "
"Prepared transactions will be
replicated at COMMIT PREPARED.");
pg_log_warning_hint("You can use '--enable-two_phase' switch
to enable two_phase.");
}

~~~

But, check what other people think just in case I am in the minority
by thinking these warnings in docs/code are still potentially useful.

======
[1]: /messages/by-id/CAFPTHDbdXa4wh01B98L8VssJnyH=uK6Qfi=sKKVXRq-9_YwXsg@mail.gmail.com

Kind Regards,
Peter Smith.
Fujitsu Australia

#35Shlok Kyal
shlok.kyal.oss@gmail.com
In reply to: Shubham Khanna (#32)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, 15 Jan 2025 at 12:03, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Tue, Jan 14, 2025 at 4:53 PM Shlok Kyal <shlok.kyal.oss@gmail.com> wrote:

On Fri, 27 Dec 2024 at 12:06, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 27, 2024 at 11:30 AM vignesh C <vignesh21@gmail.com> wrote:

The documentation requires a minor update: instead of specifying
subscriptions, the user will specify multiple databases, and the
subscription will be created on the specified databases. Documentation
should be updated accordingly:
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.

I have fixed the given comment. The attached patch contains the
suggested changes.

Hi Shubham,

I have reviewed the v9 patch. It looks fine to me. I have tested it
and it is working as expected.
I have few comments:

1.
-       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s", i,
+       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s, two_phase: %s", i,
dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-                    dbinfo[i].subconninfo);
+                    dbinfo[i].subconninfo,
+                    opt->two_phase ? "true" : "false");
Here the value of 'opt->two_phase' will be the same for all
subscriptions. So is it good to log it here? Thoughts?

Here, the 'two-phase option' is a global setting, so it makes sense to
log it consistently within the debug output. While it is true that
'opt->two_phase' will have the same value for all subscriptions,
including it in the debug log provides clarity about the replication
setup at the time of execution.
This information can be particularly useful for debugging purposes, as
it gives immediate insight into whether a 'two-phase' commit was
enabled or disabled during the setup. Therefore, logging the
'two-phase' status, even though it's consistent across subscriptions,
adds valuable context to the debug output.

2. In documentation pg_createsubscriber.sgml. Under Warning section,
we have following:

<application>pg_createsubscriber</application> sets up logical
replication with two-phase commit disabled. This means that any
prepared transactions will be replicated at the time
of <command>COMMIT PREPARED</command>, without advance preparation.
Once setup is complete, you can manually drop and re-create the
subscription(s) with
the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
option enabled.

I think we should update the documentation accordingly.

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

The attached patch contains the required changes.

Hi Shubham,

Thanks for providing the updated patch.

I have few comments:

1. When we are using '--enable-two-phase' option, I think we should
also set the 'two_phase = true' while creating logical replication
slots as we are using the same slot for the subscriber.
Currently by default it is being set to 'false':

appendPQExpBuffer(str,
"SELECT lsn FROM
pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false,
false, false)",
slot_name_esc);

2. Regarding the documentation and warning message regarding the two
phase option, I agreed with the suggestions given by Peter in [1]/messages/by-id/CAHut+PviJDKW0ftZSg1cX4XK5EJmhGW-Uh3wGrRE4ktFrnxn9w@mail.gmail.com.

[1]: /messages/by-id/CAHut+PviJDKW0ftZSg1cX4XK5EJmhGW-Uh3wGrRE4ktFrnxn9w@mail.gmail.com

Thanks and Regards,
Shlok Kyal

#36Shubham Khanna
khannashubham1197@gmail.com
In reply to: Ajin Cherian (#33)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, Jan 15, 2025 at 3:54 PM Ajin Cherian <itsajin@gmail.com> wrote:

On Wed, Jan 15, 2025 at 5:33 PM Shubham Khanna <khannashubham1197@gmail.com> wrote:

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

Hi Shubham,

Even though the documentation is updated, the actual code still gives a warning, when you try and create pg_createsubscriber with the --enable-two-phase option:

pg_createsubscriber: warning: two_phase option will not be enabled for replication slots
pg_createsubscriber: detail: Subscriptions will be created with the two_phase option disabled. Prepared transactions will be replicated at COMMIT PREPARED.

This is coming from code in check_publisher()

if (max_prepared_transactions != 0)
{
pg_log_warning("two_phase option will not be enabled for replication slots");
pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled. "
"Prepared transactions will be replicated at COMMIT PREPARED.");
}

I think this code needs to be updated as well.

Fixed the given comment. The attached patch contains the required changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v11-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v11-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From 6ade1d76cc2202f47437fc69f503b7cf5667d255 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v11] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 18 ++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 65 ++++++++++++-------
 .../t/040_pg_createsubscriber.pl              | 12 +++-
 3 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e..a6b74cdd13 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -360,6 +375,7 @@ PostgreSQL documentation
    </para>
 
    <para>
+    Unless the '--enable-two-phase' switch is specified,
     <application>pg_createsubscriber</application> sets up logical
     replication with two-phase commit disabled.  This means that any
     prepared transactions will be replicated at the time
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index faf18ccf13..3ddb10db05 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -75,18 +76,20 @@ static uint64 get_standby_sysid(const char *datadir);
 static void modify_subscriber_sysid(const struct CreateSubscriberOptions *opt);
 static bool server_is_in_recovery(PGconn *conn);
 static char *generate_object_name(PGconn *conn);
-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo,
+							const struct CreateSubscriberOptions *opt);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
 										  const char *slotname);
 static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
 static char *create_logical_replication_slot(PGconn *conn,
-											 struct LogicalRepInfo *dbinfo);
+											 struct LogicalRepInfo *dbinfo,
+											 bool two_phase);
 static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
 								  const char *slot_name);
 static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +101,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +232,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +485,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -730,7 +737,7 @@ generate_object_name(PGconn *conn)
  * set_replication_progress).
  */
 static char *
-setup_publisher(struct LogicalRepInfo *dbinfo)
+setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	char	   *lsn = NULL;
 
@@ -770,7 +777,7 @@ setup_publisher(struct LogicalRepInfo *dbinfo)
 		/* Create replication slot on publisher */
 		if (lsn)
 			pg_free(lsn);
-		lsn = create_logical_replication_slot(conn, &dbinfo[i]);
+		lsn = create_logical_replication_slot(conn, &dbinfo[i], two_phase);
 		if (lsn != NULL || dry_run)
 			pg_log_info("create replication slot \"%s\" on publisher",
 						dbinfo[i].replslotname);
@@ -837,7 +844,8 @@ server_is_in_recovery(PGconn *conn)
  * XXX Does it not allow a synchronous replica?
  */
 static void
-check_publisher(const struct LogicalRepInfo *dbinfo)
+check_publisher(const struct LogicalRepInfo *dbinfo,
+				const struct CreateSubscriberOptions *opt)
 {
 	PGconn	   *conn;
 	PGresult   *res;
@@ -932,11 +940,12 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 		failed = true;
 	}
 
-	if (max_prepared_transactions != 0)
+	if (max_prepared_transactions != 0 && !opt->two_phase)
 	{
 		pg_log_warning("two_phase option will not be enabled for replication slots");
 		pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled.  "
 							  "Prepared transactions will be replicated at COMMIT PREPARED.");
+		pg_log_warning_hint("You can use '--enable-two_phase' switch to enable two_phase.");
 	}
 
 	pg_free(wal_level);
@@ -1138,7 +1147,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1172,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1310,7 +1320,8 @@ drop_failover_replication_slots(struct LogicalRepInfo *dbinfo)
  * result set that contains the LSN.
  */
 static char *
-create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
+create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
+								bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res = NULL;
@@ -1326,8 +1337,9 @@ create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
 	slot_name_esc = PQescapeLiteral(conn, slot_name, strlen(slot_name));
 
 	appendPQExpBuffer(str,
-					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, false, false)",
-					  slot_name_esc);
+					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, %s, false)",
+					  slot_name_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(slot_name_esc);
 
@@ -1677,7 +1689,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1712,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1886,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1942,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1965,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +2002,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2194,7 +2213,7 @@ main(int argc, char **argv)
 	check_subscriber(dbinfo);
 
 	/* Check if the primary server is ready for logical replication */
-	check_publisher(dbinfo);
+	check_publisher(dbinfo, &opt);
 
 	/*
 	 * Stop the target server. The recovery process requires that the server
@@ -2207,7 +2226,7 @@ main(int argc, char **argv)
 	stop_standby_server(subscriber_dir);
 
 	/* Create the required objects for each database on publisher */
-	consistent_lsn = setup_publisher(dbinfo);
+	consistent_lsn = setup_publisher(dbinfo, opt.two_phase);
 
 	/* Write the required recovery parameters */
 	setup_recovery(dbinfo, subscriber_dir, consistent_lsn);
@@ -2229,7 +2248,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 5426159fa5..bd9a4d5032 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.34.1

#37Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#34)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Thu, Jan 16, 2025 at 4:53 AM Peter Smith <smithpb2250@gmail.com> wrote:

On Wed, Jan 15, 2025 at 9:24 PM Ajin Cherian <itsajin@gmail.com> wrote:

On Wed, Jan 15, 2025 at 5:33 PM Shubham Khanna <khannashubham1197@gmail.com> wrote:

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

Hi Shubham,

Even though the documentation is updated, the actual code still gives a warning, when you try and create pg_createsubscriber with the --enable-two-phase option:

pg_createsubscriber: warning: two_phase option will not be enabled for replication slots
pg_createsubscriber: detail: Subscriptions will be created with the two_phase option disabled. Prepared transactions will be replicated at COMMIT PREPARED.

This is coming from code in check_publisher()

if (max_prepared_transactions != 0)
{
pg_log_warning("two_phase option will not be enabled for replication slots");
pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled. "
"Prepared transactions will be replicated at COMMIT PREPARED.");
}

I think this code needs to be updated as well.

Hi Shubham.

I'm not so sure about the documentation change of v10.

Sure, we can remove the warnings in the docs and just assume/expect
the user to be aware that there is a new '--enable-two-phase' option
that they should be using. That is what v10 is now doing.

OTOH, if the user (accidentally?) omits the new '--enable-two-phase'
switch then all this two-phase documentation still seems relevant.

~

In other words, we could've left all this documented information
intact, but just qualify the paragraph. For example:

CURRENTLY (v9)
pg_createsubscriber sets up logical replication with two-phase commit
disabled. This means that any prepared transactions will be replicated
at the time of COMMIT PREPARED, without advance preparation. Once
setup is complete, you can manually drop and re-create the
subscription(s) with the two_phase option enabled.

SUGGESTION
Unless the '--enable-two-phase' switch is specified,
pg_createsubscriber sets up ...

~

Similarly, to help (accident prone?) users we could leave this code
warning [1] intact, but just change the condition slightly, and add a
helpful hint on how to avoid the problem.

CURRENTLY (v10)
if (max_prepared_transactions != 0)
{
pg_log_warning("two_phase option will not be enabled for
replication slots");
pg_log_warning_detail("Subscriptions will be created with the
two_phase option disabled. "
"Prepared transactions will be
replicated at COMMIT PREPARED.");
}

SUGGESTION
if (max_prepared_transactions != 0 && !opt->two_phase)
{
pg_log_warning("two_phase option will not be enabled for
replication slots");
pg_log_warning_detail("Subscriptions will be created with the
two_phase option disabled. "
"Prepared transactions will be
replicated at COMMIT PREPARED.");
pg_log_warning_hint("You can use '--enable-two_phase' switch
to enable two_phase.");
}

~~~

But, check what other people think just in case I am in the minority
by thinking these warnings in docs/code are still potentially useful.

I agree with your suggestions. I have used your suggestions and added
them to the latest patch.
The v11 version patch attached at [1]/messages/by-id/CAHv8Rj+Zqc9qVEaJDW03Cux_S_CMHWNM056qqJi5+z9vSYgtew@mail.gmail.com has the changes for the same.

[1]: /messages/by-id/CAHv8Rj+Zqc9qVEaJDW03Cux_S_CMHWNM056qqJi5+z9vSYgtew@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#38Shubham Khanna
khannashubham1197@gmail.com
In reply to: Shlok Kyal (#35)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Thu, Jan 16, 2025 at 3:15 PM Shlok Kyal <shlok.kyal.oss@gmail.com> wrote:

On Wed, 15 Jan 2025 at 12:03, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Tue, Jan 14, 2025 at 4:53 PM Shlok Kyal <shlok.kyal.oss@gmail.com> wrote:

On Fri, 27 Dec 2024 at 12:06, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Dec 27, 2024 at 11:30 AM vignesh C <vignesh21@gmail.com> wrote:

The documentation requires a minor update: instead of specifying
subscriptions, the user will specify multiple databases, and the
subscription will be created on the specified databases. Documentation
should be updated accordingly:
+      <para>
+       Enables <link
linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. If there are multiple subscriptions
+       specified, this option applies to all of them.
+       The default is <literal>false</literal>.

I have fixed the given comment. The attached patch contains the
suggested changes.

Hi Shubham,

I have reviewed the v9 patch. It looks fine to me. I have tested it
and it is working as expected.
I have few comments:

1.
-       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s", i,
+       pg_log_debug("subscriber(%d): subscription: %s ; connection
string: %s, two_phase: %s", i,
dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-                    dbinfo[i].subconninfo);
+                    dbinfo[i].subconninfo,
+                    opt->two_phase ? "true" : "false");
Here the value of 'opt->two_phase' will be the same for all
subscriptions. So is it good to log it here? Thoughts?

Here, the 'two-phase option' is a global setting, so it makes sense to
log it consistently within the debug output. While it is true that
'opt->two_phase' will have the same value for all subscriptions,
including it in the debug log provides clarity about the replication
setup at the time of execution.
This information can be particularly useful for debugging purposes, as
it gives immediate insight into whether a 'two-phase' commit was
enabled or disabled during the setup. Therefore, logging the
'two-phase' status, even though it's consistent across subscriptions,
adds valuable context to the debug output.

2. In documentation pg_createsubscriber.sgml. Under Warning section,
we have following:

<application>pg_createsubscriber</application> sets up logical
replication with two-phase commit disabled. This means that any
prepared transactions will be replicated at the time
of <command>COMMIT PREPARED</command>, without advance preparation.
Once setup is complete, you can manually drop and re-create the
subscription(s) with
the <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
option enabled.

I think we should update the documentation accordingly.

Previously, the warning was necessary because the 'two-phase' option
was not available, and users needed to be informed about the default
behavior regarding 'two-phase' commit. However, with the recent
addition of the 'two-phase' option, users can now directly configure
this behavior during the setup process.
Given this enhancement, the warning message is no longer relevant and
should be removed from the documentation to reflect the latest changes
accurately. Updating the documentation will help ensure that it aligns
with the current functionality and avoids any potential confusion for
users.

The attached patch contains the required changes.

Hi Shubham,

Thanks for providing the updated patch.

I have few comments:

1. When we are using '--enable-two-phase' option, I think we should
also set the 'two_phase = true' while creating logical replication
slots as we are using the same slot for the subscriber.
Currently by default it is being set to 'false':

appendPQExpBuffer(str,
"SELECT lsn FROM
pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false,
false, false)",
slot_name_esc);

2. Regarding the documentation and warning message regarding the two
phase option, I agreed with the suggestions given by Peter in [1].

[1]: /messages/by-id/CAHut+PviJDKW0ftZSg1cX4XK5EJmhGW-Uh3wGrRE4ktFrnxn9w@mail.gmail.com

Following our previous discussions, I have incorporated the necessary
changes to ensure that when the '--enable-two-phase' option is used,
the logical replication slots are also created with the 'two_phase =
true' setting. This addresses the current behavior where 'two_phase'
is set to false by default.

The v11 version patch attached at [1]/messages/by-id/CAHv8Rj+Zqc9qVEaJDW03Cux_S_CMHWNM056qqJi5+z9vSYgtew@mail.gmail.com has the changes for the same.

[1]: /messages/by-id/CAHv8Rj+Zqc9qVEaJDW03Cux_S_CMHWNM056qqJi5+z9vSYgtew@mail.gmail.com

Thanks and Regards,
Shubham Khanna.

#39Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#36)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham,

Some review comments for patch v11-0001.

======
src/sgml/ref/pg_createsubscriber.sgml

1.
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>

The missing word "switch"?

/If you are planning to use the <option>--enable-two-phase</option>/If
you are planning to use the <option>--enable-two-phase</option>
switch/

2.
<para>
+ Unless the '--enable-two-phase' switch is specified,
<application>pg_createsubscriber</application> sets up logical

Remove the single quotes, and use the proper SGML markup same as other
places where --enable-two-phase is mentioned.

<option>--enable-two-phase</option>

======

3.
+ pg_log_warning_hint("You can use '--enable-two_phase' switch to
enable two_phase.");

3a.
Typo. The real switch name does not have underscores.

/--enable-two_phase/--enable-two-phase/

~

3b.
I checked other PG source code, but I couldn't find any examples where
the switch is given in single quotes quite like this.

Maybe choose from one of the below instead:

SUGGESTION #1
Use the \"--enable-two-phase\" switch to enable two_phase.

SUGGESTION #2
Use the --enable-two-phase switch to enable two_phase.

======
Kind Regards,
Peter Smith.
Fujitsu Australia

#40Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#39)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, Jan 17, 2025 at 5:43 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Some review comments for patch v11-0001.

======
src/sgml/ref/pg_createsubscriber.sgml

1.
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
</para>

The missing word "switch"?

/If you are planning to use the <option>--enable-two-phase</option>/If
you are planning to use the <option>--enable-two-phase</option>
switch/

2.
<para>
+ Unless the '--enable-two-phase' switch is specified,
<application>pg_createsubscriber</application> sets up logical

Remove the single quotes, and use the proper SGML markup same as other
places where --enable-two-phase is mentioned.

<option>--enable-two-phase</option>

======

3.
+ pg_log_warning_hint("You can use '--enable-two_phase' switch to
enable two_phase.");

3a.
Typo. The real switch name does not have underscores.

/--enable-two_phase/--enable-two-phase/

~

3b.
I checked other PG source code, but I couldn't find any examples where
the switch is given in single quotes quite like this.

Maybe choose from one of the below instead:

SUGGESTION #1
Use the \"--enable-two-phase\" switch to enable two_phase.

SUGGESTION #2
Use the --enable-two-phase switch to enable two_phase.

Fixed the given comments. The attached patch contains the required changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v12-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v12-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From f5a14a67d0cbd93c78e5303d94026654c9891dc5 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v12] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 18 ++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 65 ++++++++++++-------
 .../t/040_pg_createsubscriber.pl              | 12 +++-
 3 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e..ee3b6678f6 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> switch then you will also need to set
+    the <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -360,6 +375,7 @@ PostgreSQL documentation
    </para>
 
    <para>
+    Unless the <option>--enable-two-phase</option> switch is specified,
     <application>pg_createsubscriber</application> sets up logical
     replication with two-phase commit disabled.  This means that any
     prepared transactions will be replicated at the time
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index faf18ccf13..69dc9fb1ac 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -75,18 +76,20 @@ static uint64 get_standby_sysid(const char *datadir);
 static void modify_subscriber_sysid(const struct CreateSubscriberOptions *opt);
 static bool server_is_in_recovery(PGconn *conn);
 static char *generate_object_name(PGconn *conn);
-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo,
+							const struct CreateSubscriberOptions *opt);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
 										  const char *slotname);
 static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
 static char *create_logical_replication_slot(PGconn *conn,
-											 struct LogicalRepInfo *dbinfo);
+											 struct LogicalRepInfo *dbinfo,
+											 bool two_phase);
 static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
 								  const char *slot_name);
 static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +101,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +232,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +485,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -730,7 +737,7 @@ generate_object_name(PGconn *conn)
  * set_replication_progress).
  */
 static char *
-setup_publisher(struct LogicalRepInfo *dbinfo)
+setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	char	   *lsn = NULL;
 
@@ -770,7 +777,7 @@ setup_publisher(struct LogicalRepInfo *dbinfo)
 		/* Create replication slot on publisher */
 		if (lsn)
 			pg_free(lsn);
-		lsn = create_logical_replication_slot(conn, &dbinfo[i]);
+		lsn = create_logical_replication_slot(conn, &dbinfo[i], two_phase);
 		if (lsn != NULL || dry_run)
 			pg_log_info("create replication slot \"%s\" on publisher",
 						dbinfo[i].replslotname);
@@ -837,7 +844,8 @@ server_is_in_recovery(PGconn *conn)
  * XXX Does it not allow a synchronous replica?
  */
 static void
-check_publisher(const struct LogicalRepInfo *dbinfo)
+check_publisher(const struct LogicalRepInfo *dbinfo,
+				const struct CreateSubscriberOptions *opt)
 {
 	PGconn	   *conn;
 	PGresult   *res;
@@ -932,11 +940,12 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 		failed = true;
 	}
 
-	if (max_prepared_transactions != 0)
+	if (max_prepared_transactions != 0 && !opt->two_phase)
 	{
 		pg_log_warning("two_phase option will not be enabled for replication slots");
 		pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled.  "
 							  "Prepared transactions will be replicated at COMMIT PREPARED.");
+		pg_log_warning_hint("You can use --enable-two-phase switch to enable two_phase.");
 	}
 
 	pg_free(wal_level);
@@ -1138,7 +1147,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1172,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1310,7 +1320,8 @@ drop_failover_replication_slots(struct LogicalRepInfo *dbinfo)
  * result set that contains the LSN.
  */
 static char *
-create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
+create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
+								bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res = NULL;
@@ -1326,8 +1337,9 @@ create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
 	slot_name_esc = PQescapeLiteral(conn, slot_name, strlen(slot_name));
 
 	appendPQExpBuffer(str,
-					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, false, false)",
-					  slot_name_esc);
+					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, %s, false)",
+					  slot_name_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(slot_name_esc);
 
@@ -1677,7 +1689,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1712,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1886,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1942,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1965,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +2002,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2194,7 +2213,7 @@ main(int argc, char **argv)
 	check_subscriber(dbinfo);
 
 	/* Check if the primary server is ready for logical replication */
-	check_publisher(dbinfo);
+	check_publisher(dbinfo, &opt);
 
 	/*
 	 * Stop the target server. The recovery process requires that the server
@@ -2207,7 +2226,7 @@ main(int argc, char **argv)
 	stop_standby_server(subscriber_dir);
 
 	/* Create the required objects for each database on publisher */
-	consistent_lsn = setup_publisher(dbinfo);
+	consistent_lsn = setup_publisher(dbinfo, opt.two_phase);
 
 	/* Write the required recovery parameters */
 	setup_recovery(dbinfo, subscriber_dir, consistent_lsn);
@@ -2229,7 +2248,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 5426159fa5..bd9a4d5032 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#41Shlok Kyal
shlok.kyal.oss@gmail.com
In reply to: Shubham Khanna (#40)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Fri, 17 Jan 2025 at 09:52, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Jan 17, 2025 at 5:43 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Some review comments for patch v11-0001.

======
src/sgml/ref/pg_createsubscriber.sgml

1.
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
</para>

The missing word "switch"?

/If you are planning to use the <option>--enable-two-phase</option>/If
you are planning to use the <option>--enable-two-phase</option>
switch/

2.
<para>
+ Unless the '--enable-two-phase' switch is specified,
<application>pg_createsubscriber</application> sets up logical

Remove the single quotes, and use the proper SGML markup same as other
places where --enable-two-phase is mentioned.

<option>--enable-two-phase</option>

======

3.
+ pg_log_warning_hint("You can use '--enable-two_phase' switch to
enable two_phase.");

3a.
Typo. The real switch name does not have underscores.

/--enable-two_phase/--enable-two-phase/

~

3b.
I checked other PG source code, but I couldn't find any examples where
the switch is given in single quotes quite like this.

Maybe choose from one of the below instead:

SUGGESTION #1
Use the \"--enable-two-phase\" switch to enable two_phase.

SUGGESTION #2
Use the --enable-two-phase switch to enable two_phase.

Fixed the given comments. The attached patch contains the required changes.

Hi Shubham,

I have a comment for the v12 patch.
I think we can pass just the two_phase option instead of the whole
structure here. As we are just using 'opt->two_phase'.

+check_publisher(const struct LogicalRepInfo *dbinfo,
+               const struct CreateSubscriberOptions *opt)

Thanks and Regards,
Shlok Kyal

#42Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#40)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi Shubham,

Patch v12-0001 LGTM.

BTW, there is a precedent for passing the 'opt' arg but then only
using one member from it. See function wait_for_end_recovery in this
same file. So whether you decide to change the code per Shlok's review
[1]: /messages/by-id/CANhcyEXQ1h=oSPFFziCZuU6far6a82DQafL0S85CyVRyEntA+w@mail.gmail.com

======
[1]: /messages/by-id/CANhcyEXQ1h=oSPFFziCZuU6far6a82DQafL0S85CyVRyEntA+w@mail.gmail.com

Kind Regards,
Peter Smith.
Fujitsu Australia

#43Shubham Khanna
khannashubham1197@gmail.com
In reply to: Shlok Kyal (#41)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, Jan 20, 2025 at 1:34 AM Shlok Kyal <shlok.kyal.oss@gmail.com> wrote:

On Fri, 17 Jan 2025 at 09:52, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

On Fri, Jan 17, 2025 at 5:43 AM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Some review comments for patch v11-0001.

======
src/sgml/ref/pg_createsubscriber.sgml

1.
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> then you will also need to set the
+    <xref linkend="guc-max-prepared-transactions"/> appropriately.
</para>

The missing word "switch"?

/If you are planning to use the <option>--enable-two-phase</option>/If
you are planning to use the <option>--enable-two-phase</option>
switch/

2.
<para>
+ Unless the '--enable-two-phase' switch is specified,
<application>pg_createsubscriber</application> sets up logical

Remove the single quotes, and use the proper SGML markup same as other
places where --enable-two-phase is mentioned.

<option>--enable-two-phase</option>

======

3.
+ pg_log_warning_hint("You can use '--enable-two_phase' switch to
enable two_phase.");

3a.
Typo. The real switch name does not have underscores.

/--enable-two_phase/--enable-two-phase/

~

3b.
I checked other PG source code, but I couldn't find any examples where
the switch is given in single quotes quite like this.

Maybe choose from one of the below instead:

SUGGESTION #1
Use the \"--enable-two-phase\" switch to enable two_phase.

SUGGESTION #2
Use the --enable-two-phase switch to enable two_phase.

Fixed the given comments. The attached patch contains the required changes.

Hi Shubham,

I have a comment for the v12 patch.
I think we can pass just the two_phase option instead of the whole
structure here. As we are just using 'opt->two_phase'.

+check_publisher(const struct LogicalRepInfo *dbinfo,
+               const struct CreateSubscriberOptions *opt)

Fixed the given comment. The attached patch contains the required changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v13-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v13-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From 56e6442f4421d4adc9340f02886c027f329eaee9 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Fri, 22 Nov 2024 12:03:13 +0530
Subject: [PATCH v13] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 18 ++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 65 ++++++++++++-------
 .../t/040_pg_createsubscriber.pl              | 12 +++-
 3 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e..ee3b6678f6 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> switch then you will also need to set
+    the <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -360,6 +375,7 @@ PostgreSQL documentation
    </para>
 
    <para>
+    Unless the <option>--enable-two-phase</option> switch is specified,
     <application>pg_createsubscriber</application> sets up logical
     replication with two-phase commit disabled.  This means that any
     prepared transactions will be replicated at the time
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index faf18ccf13..da9bb948a7 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -75,18 +76,20 @@ static uint64 get_standby_sysid(const char *datadir);
 static void modify_subscriber_sysid(const struct CreateSubscriberOptions *opt);
 static bool server_is_in_recovery(PGconn *conn);
 static char *generate_object_name(PGconn *conn);
-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo,
+							bool two_phase);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
 										  const char *slotname);
 static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
 static char *create_logical_replication_slot(PGconn *conn,
-											 struct LogicalRepInfo *dbinfo);
+											 struct LogicalRepInfo *dbinfo,
+											 bool two_phase);
 static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
 								  const char *slot_name);
 static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +101,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +232,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +485,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -730,7 +737,7 @@ generate_object_name(PGconn *conn)
  * set_replication_progress).
  */
 static char *
-setup_publisher(struct LogicalRepInfo *dbinfo)
+setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	char	   *lsn = NULL;
 
@@ -770,7 +777,7 @@ setup_publisher(struct LogicalRepInfo *dbinfo)
 		/* Create replication slot on publisher */
 		if (lsn)
 			pg_free(lsn);
-		lsn = create_logical_replication_slot(conn, &dbinfo[i]);
+		lsn = create_logical_replication_slot(conn, &dbinfo[i], two_phase);
 		if (lsn != NULL || dry_run)
 			pg_log_info("create replication slot \"%s\" on publisher",
 						dbinfo[i].replslotname);
@@ -837,7 +844,8 @@ server_is_in_recovery(PGconn *conn)
  * XXX Does it not allow a synchronous replica?
  */
 static void
-check_publisher(const struct LogicalRepInfo *dbinfo)
+check_publisher(const struct LogicalRepInfo *dbinfo,
+				bool two_phase)
 {
 	PGconn	   *conn;
 	PGresult   *res;
@@ -932,11 +940,12 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 		failed = true;
 	}
 
-	if (max_prepared_transactions != 0)
+	if (max_prepared_transactions != 0 && !two_phase)
 	{
 		pg_log_warning("two_phase option will not be enabled for replication slots");
 		pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled.  "
 							  "Prepared transactions will be replicated at COMMIT PREPARED.");
+		pg_log_warning_hint("You can use --enable-two-phase switch to enable two_phase.");
 	}
 
 	pg_free(wal_level);
@@ -1138,7 +1147,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1172,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1310,7 +1320,8 @@ drop_failover_replication_slots(struct LogicalRepInfo *dbinfo)
  * result set that contains the LSN.
  */
 static char *
-create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
+create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
+								bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res = NULL;
@@ -1326,8 +1337,9 @@ create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
 	slot_name_esc = PQescapeLiteral(conn, slot_name, strlen(slot_name));
 
 	appendPQExpBuffer(str,
-					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, false, false)",
-					  slot_name_esc);
+					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, %s, false)",
+					  slot_name_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(slot_name_esc);
 
@@ -1677,7 +1689,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1712,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1886,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1942,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1965,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +2002,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2194,7 +2213,7 @@ main(int argc, char **argv)
 	check_subscriber(dbinfo);
 
 	/* Check if the primary server is ready for logical replication */
-	check_publisher(dbinfo);
+	check_publisher(dbinfo, opt.two_phase);
 
 	/*
 	 * Stop the target server. The recovery process requires that the server
@@ -2207,7 +2226,7 @@ main(int argc, char **argv)
 	stop_standby_server(subscriber_dir);
 
 	/* Create the required objects for each database on publisher */
-	consistent_lsn = setup_publisher(dbinfo);
+	consistent_lsn = setup_publisher(dbinfo, opt.two_phase);
 
 	/* Write the required recovery parameters */
 	setup_recovery(dbinfo, subscriber_dir, consistent_lsn);
@@ -2229,7 +2248,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index 5426159fa5..bd9a4d5032 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -357,6 +357,7 @@ command_ok(
 	'run pg_createsubscriber without --databases');
 
 # Run pg_createsubscriber on node S
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber', '--verbose',
@@ -371,7 +372,7 @@ command_ok(
 		'replslot1', '--replication-slot',
 		'replslot2', '--database',
 		$db1, '--database',
-		$db2
+		$db2, '--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -390,6 +391,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#44Shubham Khanna
khannashubham1197@gmail.com
In reply to: Peter Smith (#42)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, Jan 20, 2025 at 12:00 PM Peter Smith <smithpb2250@gmail.com> wrote:

Hi Shubham,

Patch v12-0001 LGTM.

BTW, there is a precedent for passing the 'opt' arg but then only
using one member from it. See function wait_for_end_recovery in this
same file. So whether you decide to change the code per Shlok's review
[1], or decide not to change it -- either way is OK for me.

======
[1] /messages/by-id/CANhcyEXQ1h=oSPFFziCZuU6far6a82DQafL0S85CyVRyEntA+w@mail.gmail.com

I have incorporated Shlok's suggestion [1]/messages/by-id/CANhcyEXQ1h=oSPFFziCZuU6far6a82DQafL0S85CyVRyEntA+w@mail.gmail.com into the patch and made the
necessary updates.
The attached v13 patch at [2]/messages/by-id/CAHv8RjJfaHFMeDkHOdMpLUHCeDN+e5p0CsdQxcKEOiWRnRjxfg@mail.gmail.com contains the updated changes.

[1]: /messages/by-id/CANhcyEXQ1h=oSPFFziCZuU6far6a82DQafL0S85CyVRyEntA+w@mail.gmail.com
[2]: /messages/by-id/CAHv8RjJfaHFMeDkHOdMpLUHCeDN+e5p0CsdQxcKEOiWRnRjxfg@mail.gmail.com

Thanks and regards,
Shubham Khanna.

#45Shubham Khanna
khannashubham1197@gmail.com
In reply to: Shubham Khanna (#44)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Hi,

Following the recent comments on Patch v13-0001, the patch was not
applied to the HEAD. I have addressed the feedback and prepared a
rebased version to incorporate the necessary adjustments.
Please find the updated patch.

Thanks and regards,
Shubham Khanna.

Attachments:

v14-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v14-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From 3ac74cf3b41838ada077e082ad042f3a92187206 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Wed, 22 Jan 2025 16:09:52 +0530
Subject: [PATCH v14] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 18 +++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 63 ++++++++++++-------
 .../t/040_pg_createsubscriber.pl              | 11 ++++
 3 files changed, 68 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e..ee3b6678f6 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> switch then you will also need to set
+    the <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -360,6 +375,7 @@ PostgreSQL documentation
    </para>
 
    <para>
+    Unless the <option>--enable-two-phase</option> switch is specified,
     <application>pg_createsubscriber</application> sets up logical
     replication with two-phase commit disabled.  This means that any
     prepared transactions will be replicated at the time
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index faf18ccf13..60ee3d2373 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -75,18 +76,19 @@ static uint64 get_standby_sysid(const char *datadir);
 static void modify_subscriber_sysid(const struct CreateSubscriberOptions *opt);
 static bool server_is_in_recovery(PGconn *conn);
 static char *generate_object_name(PGconn *conn);
-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo, bool two_phase);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
 										  const char *slotname);
 static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
 static char *create_logical_replication_slot(PGconn *conn,
-											 struct LogicalRepInfo *dbinfo);
+											 struct LogicalRepInfo *dbinfo,
+											 bool two_phase);
 static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
 								  const char *slot_name);
 static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +100,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +231,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +484,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -730,7 +736,7 @@ generate_object_name(PGconn *conn)
  * set_replication_progress).
  */
 static char *
-setup_publisher(struct LogicalRepInfo *dbinfo)
+setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	char	   *lsn = NULL;
 
@@ -770,7 +776,7 @@ setup_publisher(struct LogicalRepInfo *dbinfo)
 		/* Create replication slot on publisher */
 		if (lsn)
 			pg_free(lsn);
-		lsn = create_logical_replication_slot(conn, &dbinfo[i]);
+		lsn = create_logical_replication_slot(conn, &dbinfo[i], two_phase);
 		if (lsn != NULL || dry_run)
 			pg_log_info("create replication slot \"%s\" on publisher",
 						dbinfo[i].replslotname);
@@ -837,7 +843,7 @@ server_is_in_recovery(PGconn *conn)
  * XXX Does it not allow a synchronous replica?
  */
 static void
-check_publisher(const struct LogicalRepInfo *dbinfo)
+check_publisher(const struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	PGconn	   *conn;
 	PGresult   *res;
@@ -932,11 +938,12 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 		failed = true;
 	}
 
-	if (max_prepared_transactions != 0)
+	if (max_prepared_transactions != 0 && !two_phase)
 	{
 		pg_log_warning("two_phase option will not be enabled for replication slots");
 		pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled.  "
 							  "Prepared transactions will be replicated at COMMIT PREPARED.");
+		pg_log_warning_hint("You can use --enable-two-phase switch to enable two_phase.");
 	}
 
 	pg_free(wal_level);
@@ -1138,7 +1145,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1162,7 +1170,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1310,7 +1318,8 @@ drop_failover_replication_slots(struct LogicalRepInfo *dbinfo)
  * result set that contains the LSN.
  */
 static char *
-create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
+create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
+								bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res = NULL;
@@ -1326,8 +1335,9 @@ create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
 	slot_name_esc = PQescapeLiteral(conn, slot_name, strlen(slot_name));
 
 	appendPQExpBuffer(str,
-					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, false, false)",
-					  slot_name_esc);
+					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, %s, false)",
+					  slot_name_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(slot_name_esc);
 
@@ -1677,7 +1687,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1699,8 +1710,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	pg_free(pubname_esc);
 	pg_free(subname_esc);
@@ -1872,6 +1884,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1927,6 +1940,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1949,7 +1963,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1986,6 +2000,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2194,7 +2211,7 @@ main(int argc, char **argv)
 	check_subscriber(dbinfo);
 
 	/* Check if the primary server is ready for logical replication */
-	check_publisher(dbinfo);
+	check_publisher(dbinfo, opt.two_phase);
 
 	/*
 	 * Stop the target server. The recovery process requires that the server
@@ -2207,7 +2224,7 @@ main(int argc, char **argv)
 	stop_standby_server(subscriber_dir);
 
 	/* Create the required objects for each database on publisher */
-	consistent_lsn = setup_publisher(dbinfo);
+	consistent_lsn = setup_publisher(dbinfo, opt.two_phase);
 
 	/* Write the required recovery parameters */
 	setup_recovery(dbinfo, subscriber_dir, consistent_lsn);
@@ -2229,7 +2246,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index c8dbdb7e9b..c35fa108ce 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -373,6 +373,7 @@ command_ok(
 
 # Run pg_createsubscriber on node S.  --verbose is used twice
 # to show more information.
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber',
@@ -388,6 +389,7 @@ command_ok(
 		'--replication-slot' => 'replslot2',
 		'--database' => $db1,
 		'--database' => $db2,
+		'--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -406,6 +408,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#46Peter Smith
smithpb2250@gmail.com
In reply to: Shubham Khanna (#45)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

Patch v14-0001 LGTM

======
Kind Regards,
Peter Smith.
Fujitsu Australia

#47Shlok Kyal
shlok.kyal.oss@gmail.com
In reply to: Shubham Khanna (#45)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, 22 Jan 2025 at 16:23, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

Hi,

Following the recent comments on Patch v13-0001, the patch was not
applied to the HEAD. I have addressed the feedback and prepared a
rebased version to incorporate the necessary adjustments.
Please find the updated patch.

Thanks and regards,
Shubham Khanna.

Hi Shubham,

I reviewed the patch and it looks good to me.

Thanks and Regards,
Shlok Kyal

#48vignesh C
vignesh21@gmail.com
In reply to: Shubham Khanna (#45)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Wed, 22 Jan 2025 at 16:23, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

Hi,

Following the recent comments on Patch v13-0001, the patch was not
applied to the HEAD. I have addressed the feedback and prepared a
rebased version to incorporate the necessary adjustments.
Please find the updated patch.

The patch needs to be rebased:
git am v14-0001-Add-support-for-two-phase-commit-in-pg_createsub.patch
Applying: Add support for two-phase commit in pg_createsubscriber
error: patch failed: src/bin/pg_basebackup/pg_createsubscriber.c:1326
error: src/bin/pg_basebackup/pg_createsubscriber.c: patch does not apply

Regards,
Vignesh

#49Shubham Khanna
khannashubham1197@gmail.com
In reply to: vignesh C (#48)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, Feb 17, 2025 at 9:43 PM vignesh C <vignesh21@gmail.com> wrote:

On Wed, 22 Jan 2025 at 16:23, Shubham Khanna
<khannashubham1197@gmail.com> wrote:

Hi,

Following the recent comments on Patch v13-0001, the patch was not
applied to the HEAD. I have addressed the feedback and prepared a
rebased version to incorporate the necessary adjustments.
Please find the updated patch.

The patch needs to be rebased:
git am v14-0001-Add-support-for-two-phase-commit-in-pg_createsub.patch
Applying: Add support for two-phase commit in pg_createsubscriber
error: patch failed: src/bin/pg_basebackup/pg_createsubscriber.c:1326
error: src/bin/pg_basebackup/pg_createsubscriber.c: patch does not apply

I have addressed the feedback and prepared a rebased version to
incorporate the necessary adjustments.
Please find the updated patch.

Thanks and regards,
Shubham Khanna.

Attachments:

v15-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v15-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From 9b94a72da3cf78214b895bdf573c42c0f3ab75c9 Mon Sep 17 00:00:00 2001
From: Khanna <Shubham.Khanna@fujitsu.com>
Date: Mon, 17 Feb 2025 23:57:26 +0530
Subject: [PATCH v15] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 18 +++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 63 ++++++++++++-------
 .../t/040_pg_createsubscriber.pl              | 11 ++++
 3 files changed, 68 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index 26b8e64a4e0..ee3b6678f63 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> switch then you will also need to set
+    the <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -360,6 +375,7 @@ PostgreSQL documentation
    </para>
 
    <para>
+    Unless the <option>--enable-two-phase</option> switch is specified,
     <application>pg_createsubscriber</application> sets up logical
     replication with two-phase commit disabled.  This means that any
     prepared transactions will be replicated at the time
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index 2d881d54f5b..d8ddcec3159 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -75,18 +76,19 @@ static uint64 get_standby_sysid(const char *datadir);
 static void modify_subscriber_sysid(const struct CreateSubscriberOptions *opt);
 static bool server_is_in_recovery(PGconn *conn);
 static char *generate_object_name(PGconn *conn);
-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo, bool two_phase);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
-							 const char *consistent_lsn);
+							 const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const char *datadir,
 						   const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
 										  const char *slotname);
 static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
 static char *create_logical_replication_slot(PGconn *conn,
-											 struct LogicalRepInfo *dbinfo);
+											 struct LogicalRepInfo *dbinfo,
+											 bool two_phase);
 static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
 								  const char *slot_name);
 static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +100,9 @@ static void wait_for_end_recovery(const char *conninfo,
 								  const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+								const struct LogicalRepInfo *dbinfo,
+								bool two_phase);
 static void set_replication_progress(PGconn *conn, const struct LogicalRepInfo *dbinfo,
 									 const char *lsn);
 static void enable_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo);
@@ -227,6 +231,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +484,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 opt->two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -730,7 +736,7 @@ generate_object_name(PGconn *conn)
  * set_replication_progress).
  */
 static char *
-setup_publisher(struct LogicalRepInfo *dbinfo)
+setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	char	   *lsn = NULL;
 
@@ -770,7 +776,7 @@ setup_publisher(struct LogicalRepInfo *dbinfo)
 		/* Create replication slot on publisher */
 		if (lsn)
 			pg_free(lsn);
-		lsn = create_logical_replication_slot(conn, &dbinfo[i]);
+		lsn = create_logical_replication_slot(conn, &dbinfo[i], two_phase);
 		if (lsn != NULL || dry_run)
 			pg_log_info("create replication slot \"%s\" on publisher",
 						dbinfo[i].replslotname);
@@ -837,7 +843,7 @@ server_is_in_recovery(PGconn *conn)
  * XXX Does it not allow a synchronous replica?
  */
 static void
-check_publisher(const struct LogicalRepInfo *dbinfo)
+check_publisher(const struct LogicalRepInfo *dbinfo, bool two_phase)
 {
 	PGconn	   *conn;
 	PGresult   *res;
@@ -932,11 +938,12 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 		failed = true;
 	}
 
-	if (max_prepared_transactions != 0)
+	if (max_prepared_transactions != 0 && !two_phase)
 	{
 		pg_log_warning("two_phase option will not be enabled for replication slots");
 		pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled.  "
 							  "Prepared transactions will be replicated at COMMIT PREPARED.");
+		pg_log_warning_hint("You can use --enable-two-phase switch to enable two_phase.");
 	}
 
 	pg_free(wal_level);
@@ -1139,7 +1146,8 @@ check_and_drop_existing_subscriptions(PGconn *conn,
  * replication setup.
  */
 static void
-setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
+setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn,
+				 bool two_phase)
 {
 	for (int i = 0; i < num_dbs; i++)
 	{
@@ -1163,7 +1171,7 @@ setup_subscriber(struct LogicalRepInfo *dbinfo, const char *consistent_lsn)
 		 */
 		drop_publication(conn, &dbinfo[i]);
 
-		create_subscription(conn, &dbinfo[i]);
+		create_subscription(conn, &dbinfo[i], two_phase);
 
 		/* Set the replication progress to the correct LSN */
 		set_replication_progress(conn, &dbinfo[i], consistent_lsn);
@@ -1311,7 +1319,8 @@ drop_failover_replication_slots(struct LogicalRepInfo *dbinfo)
  * result set that contains the LSN.
  */
 static char *
-create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
+create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
+								bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res = NULL;
@@ -1327,8 +1336,9 @@ create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
 	slot_name_esc = PQescapeLiteral(conn, slot_name, strlen(slot_name));
 
 	appendPQExpBuffer(str,
-					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, false, false)",
-					  slot_name_esc);
+					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, %s, false)",
+					  slot_name_esc,
+					  two_phase ? "true" : "false");
 
 	PQfreemem(slot_name_esc);
 
@@ -1678,7 +1688,8 @@ drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo)
  * initial location.
  */
 static void
-create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
+create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo,
+					bool two_phase)
 {
 	PQExpBuffer str = createPQExpBuffer();
 	PGresult   *res;
@@ -1700,8 +1711,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  two_phase ? "true" : "false");
 
 	PQfreemem(pubname_esc);
 	PQfreemem(subname_esc);
@@ -1873,6 +1885,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1928,6 +1941,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1950,7 +1964,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -1987,6 +2001,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2195,7 +2212,7 @@ main(int argc, char **argv)
 	check_subscriber(dbinfo);
 
 	/* Check if the primary server is ready for logical replication */
-	check_publisher(dbinfo);
+	check_publisher(dbinfo, opt.two_phase);
 
 	/*
 	 * Stop the target server. The recovery process requires that the server
@@ -2208,7 +2225,7 @@ main(int argc, char **argv)
 	stop_standby_server(subscriber_dir);
 
 	/* Create the required objects for each database on publisher */
-	consistent_lsn = setup_publisher(dbinfo);
+	consistent_lsn = setup_publisher(dbinfo, opt.two_phase);
 
 	/* Write the required recovery parameters */
 	setup_recovery(dbinfo, subscriber_dir, consistent_lsn);
@@ -2230,7 +2247,7 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfo, consistent_lsn, opt.two_phase);
 
 	/* Remove primary_slot_name if it exists on primary */
 	drop_primary_replication_slot(dbinfo, primary_slot_name);
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index c8dbdb7e9b7..c35fa108ce3 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -373,6 +373,7 @@ command_ok(
 
 # Run pg_createsubscriber on node S.  --verbose is used twice
 # to show more information.
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber',
@@ -388,6 +389,7 @@ command_ok(
 		'--replication-slot' => 'replslot2',
 		'--database' => $db1,
 		'--database' => $db2,
+		'--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -406,6 +408,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.41.0.windows.3

#50Amit Kapila
amit.kapila16@gmail.com
In reply to: Shubham Khanna (#49)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Tue, Feb 18, 2025 at 12:16 AM Shubham Khanna
<khannashubham1197@gmail.com> wrote:

-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo, bool
two_phase);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
 static void check_subscriber(const struct LogicalRepInfo *dbinfo);
 static void setup_subscriber(struct LogicalRepInfo *dbinfo,
- const char *consistent_lsn);
+ const char *consistent_lsn, bool two_phase);
 static void setup_recovery(const struct LogicalRepInfo *dbinfo, const
char *datadir,
     const char *lsn);
 static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
    const char *slotname);
 static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
 static char *create_logical_replication_slot(PGconn *conn,
- struct LogicalRepInfo *dbinfo);
+ struct LogicalRepInfo *dbinfo,
+ bool two_phase);
 static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
    const char *slot_name);
 static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +100,9 @@ static void wait_for_end_recovery(const char *conninfo,
    const struct CreateSubscriberOptions *opt);
 static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
 static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct
LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+ const struct LogicalRepInfo *dbinfo,
+ bool two_phase);

Specifying two_phase as a separate parameter in so many APIs looks
odd. Wouldn't it be better to make it part of LogicalRepInfo as we do
with other parameters of CreateSubscriberOptions?

--
With Regards,
Amit Kapila.

#51Shubham Khanna
khannashubham1197@gmail.com
In reply to: Amit Kapila (#50)
1 attachment(s)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, Feb 24, 2025 at 10:57 AM Amit Kapila <amit.kapila16@gmail.com> wrote:

On Tue, Feb 18, 2025 at 12:16 AM Shubham Khanna
<khannashubham1197@gmail.com> wrote:

-static void check_publisher(const struct LogicalRepInfo *dbinfo);
-static char *setup_publisher(struct LogicalRepInfo *dbinfo);
+static void check_publisher(const struct LogicalRepInfo *dbinfo, bool
two_phase);
+static char *setup_publisher(struct LogicalRepInfo *dbinfo, bool two_phase);
static void check_subscriber(const struct LogicalRepInfo *dbinfo);
static void setup_subscriber(struct LogicalRepInfo *dbinfo,
- const char *consistent_lsn);
+ const char *consistent_lsn, bool two_phase);
static void setup_recovery(const struct LogicalRepInfo *dbinfo, const
char *datadir,
const char *lsn);
static void drop_primary_replication_slot(struct LogicalRepInfo *dbinfo,
const char *slotname);
static void drop_failover_replication_slots(struct LogicalRepInfo *dbinfo);
static char *create_logical_replication_slot(PGconn *conn,
- struct LogicalRepInfo *dbinfo);
+ struct LogicalRepInfo *dbinfo,
+ bool two_phase);
static void drop_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo,
const char *slot_name);
static void pg_ctl_status(const char *pg_ctl_cmd, int rc);
@@ -98,7 +100,9 @@ static void wait_for_end_recovery(const char *conninfo,
const struct CreateSubscriberOptions *opt);
static void create_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
static void drop_publication(PGconn *conn, struct LogicalRepInfo *dbinfo);
-static void create_subscription(PGconn *conn, const struct
LogicalRepInfo *dbinfo);
+static void create_subscription(PGconn *conn,
+ const struct LogicalRepInfo *dbinfo,
+ bool two_phase);

Specifying two_phase as a separate parameter in so many APIs looks
odd. Wouldn't it be better to make it part of LogicalRepInfo as we do
with other parameters of CreateSubscriberOptions?

I agree that passing two_phase as a separate parameter across multiple
function calls adds redundancy. It would be more consistent and
maintainable to include two_phase as part of LogicalRepInfo, similar
to how other options from CreateSubscriberOptions are handled.
I have created a new structure LogicalRepInfos to include a bool
two_phase field. This way, all functions that require two_phase can
access it directly from the LogicalRepInfos structure, reducing
unnecessary argument passing.

The attached patch contains the suggested changes.

Thanks and regards,
Shubham Khanna.

Attachments:

v16-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchapplication/octet-stream; name=v16-0001-Add-support-for-two-phase-commit-in-pg_createsub.patchDownload
From 93274d21b09dc0d66e68fe4bc0a4cd6d13829d31 Mon Sep 17 00:00:00 2001
From: Vignesh <vignesh21@gmail.com>
Date: Mon, 24 Feb 2025 13:59:12 +0530
Subject: [PATCH v16] Add support for two-phase commit in pg_createsubscriber

This patch introduces the '--enable-two-phase' option to the
'pg_createsubscriber' utility, allowing users to enable two-phase commit for
all subscriptions during their creation.

By default, two-phase commit is disabled if the option is not provided.

When two-phase commit is enabled, prepared transactions are sent to the
subscriber at the time of 'PREPARE TRANSACTION', and they are processed as
two-phase transactions on the subscriber as well. If disabled, prepared
transactions are sent only when committed and are processed immediately by the
subscriber.
---
 doc/src/sgml/ref/pg_createsubscriber.sgml     | 18 ++++-
 src/bin/pg_basebackup/pg_createsubscriber.c   | 79 ++++++++++++-------
 .../t/040_pg_createsubscriber.pl              | 11 +++
 3 files changed, 78 insertions(+), 30 deletions(-)

diff --git a/doc/src/sgml/ref/pg_createsubscriber.sgml b/doc/src/sgml/ref/pg_createsubscriber.sgml
index d56487fe2c..65caa3f509 100644
--- a/doc/src/sgml/ref/pg_createsubscriber.sgml
+++ b/doc/src/sgml/ref/pg_createsubscriber.sgml
@@ -165,6 +165,19 @@ PostgreSQL documentation
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><option>-T</option></term>
+     <term><option>--enable-two-phase</option></term>
+     <listitem>
+      <para>
+       Enables <link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>
+       commit for the subscription. When multiple databases are specified, this
+       option applies uniformly to all subscriptions created on those databases.
+       The default is <literal>false</literal>.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><option>-U <replaceable class="parameter">username</replaceable></option></term>
      <term><option>--subscriber-username=<replaceable class="parameter">username</replaceable></option></term>
@@ -300,7 +313,9 @@ PostgreSQL documentation
     greater than or equal to the number of specified databases.  The target
     server must have <xref linkend="guc-max-worker-processes"/> configured to a
     value greater than the number of specified databases.  The target server
-    must accept local connections.
+    must accept local connections. If you are planning to use the
+    <option>--enable-two-phase</option> switch then you will also need to set
+    the <xref linkend="guc-max-prepared-transactions"/> appropriately.
    </para>
 
    <para>
@@ -360,6 +375,7 @@ PostgreSQL documentation
    </para>
 
    <para>
+    Unless the <option>--enable-two-phase</option> switch is specified,
     <application>pg_createsubscriber</application> sets up logical
     replication with two-phase commit disabled.  This means that any
     prepared transactions will be replicated at the time
diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c
index 9fdf15e5ac..7445852764 100644
--- a/src/bin/pg_basebackup/pg_createsubscriber.c
+++ b/src/bin/pg_basebackup/pg_createsubscriber.c
@@ -38,6 +38,7 @@ struct CreateSubscriberOptions
 	char	   *socket_dir;		/* directory for Unix-domain socket, if any */
 	char	   *sub_port;		/* subscriber port number */
 	const char *sub_username;	/* subscriber username */
+	bool		two_phase;		/* enable-two-phase option */
 	SimpleStringList database_names;	/* list of database names */
 	SimpleStringList pub_names; /* list of publication names */
 	SimpleStringList sub_names; /* list of subscription names */
@@ -58,6 +59,12 @@ struct LogicalRepInfo
 	bool		made_publication;	/* publication was created */
 };
 
+struct LogicalRepInfos
+{
+	struct LogicalRepInfo *dbinfo;
+	bool		two_phase;		/* enable-two-phase option */
+};
+
 static void cleanup_objects_atexit(void);
 static void usage();
 static char *get_base_conninfo(const char *conninfo, char **dbname);
@@ -117,7 +124,7 @@ static bool dry_run = false;
 
 static bool success = false;
 
-static struct LogicalRepInfo *dbinfo;
+static struct LogicalRepInfos dbinfos;
 static int	num_dbs = 0;		/* number of specified databases */
 static int	num_pubs = 0;		/* number of specified publications */
 static int	num_subs = 0;		/* number of specified subscriptions */
@@ -172,17 +179,17 @@ cleanup_objects_atexit(void)
 
 	for (int i = 0; i < num_dbs; i++)
 	{
-		if (dbinfo[i].made_publication || dbinfo[i].made_replslot)
+		if (dbinfos.dbinfo[i].made_publication || dbinfos.dbinfo[i].made_replslot)
 		{
 			PGconn	   *conn;
 
-			conn = connect_database(dbinfo[i].pubconninfo, false);
+			conn = connect_database(dbinfos.dbinfo[i].pubconninfo, false);
 			if (conn != NULL)
 			{
-				if (dbinfo[i].made_publication)
-					drop_publication(conn, &dbinfo[i]);
-				if (dbinfo[i].made_replslot)
-					drop_replication_slot(conn, &dbinfo[i], dbinfo[i].replslotname);
+				if (dbinfos.dbinfo[i].made_publication)
+					drop_publication(conn, &dbinfos.dbinfo[i]);
+				if (dbinfos.dbinfo[i].made_replslot)
+					drop_replication_slot(conn, &dbinfos.dbinfo[i], dbinfos.dbinfo[i].replslotname);
 				disconnect_database(conn, false);
 			}
 			else
@@ -192,16 +199,18 @@ cleanup_objects_atexit(void)
 				 * that some objects were left on primary and should be
 				 * removed before trying again.
 				 */
-				if (dbinfo[i].made_publication)
+				if (dbinfos.dbinfo[i].made_publication)
 				{
 					pg_log_warning("publication \"%s\" created in database \"%s\" on primary was left behind",
-								   dbinfo[i].pubname, dbinfo[i].dbname);
+								   dbinfos.dbinfo[i].pubname,
+								   dbinfos.dbinfo[i].dbname);
 					pg_log_warning_hint("Drop this publication before trying again.");
 				}
-				if (dbinfo[i].made_replslot)
+				if (dbinfos.dbinfo[i].made_replslot)
 				{
 					pg_log_warning("replication slot \"%s\" created in database \"%s\" on primary was left behind",
-								   dbinfo[i].replslotname, dbinfo[i].dbname);
+								   dbinfos.dbinfo[i].replslotname,
+								   dbinfos.dbinfo[i].dbname);
 					pg_log_warning_hint("Drop this replication slot soon to avoid retention of WAL files.");
 				}
 			}
@@ -227,6 +236,7 @@ usage(void)
 	printf(_("  -P, --publisher-server=CONNSTR  publisher connection string\n"));
 	printf(_("  -s, --socketdir=DIR             socket directory to use (default current dir.)\n"));
 	printf(_("  -t, --recovery-timeout=SECS     seconds to wait for recovery to end\n"));
+	printf(_("  -T, --enable-two-phase          enable two-phase commit for all subscriptions\n"));
 	printf(_("  -U, --subscriber-username=NAME  user name for subscriber connection\n"));
 	printf(_("  -v, --verbose                   output verbose messages\n"));
 	printf(_("      --config-file=FILENAME      use specified main server configuration\n"
@@ -479,9 +489,10 @@ store_pub_sub_info(const struct CreateSubscriberOptions *opt,
 					 dbinfo[i].pubname ? dbinfo[i].pubname : "(auto)",
 					 dbinfo[i].replslotname ? dbinfo[i].replslotname : "(auto)",
 					 dbinfo[i].pubconninfo);
-		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s", i,
+		pg_log_debug("subscriber(%d): subscription: %s ; connection string: %s, two_phase: %s", i,
 					 dbinfo[i].subname ? dbinfo[i].subname : "(auto)",
-					 dbinfo[i].subconninfo);
+					 dbinfo[i].subconninfo,
+					 dbinfos.two_phase ? "true" : "false");
 
 		if (num_pubs > 0)
 			pubcell = pubcell->next;
@@ -938,11 +949,12 @@ check_publisher(const struct LogicalRepInfo *dbinfo)
 		failed = true;
 	}
 
-	if (max_prepared_transactions != 0)
+	if (max_prepared_transactions != 0 && dbinfos.two_phase)
 	{
 		pg_log_warning("two_phase option will not be enabled for replication slots");
 		pg_log_warning_detail("Subscriptions will be created with the two_phase option disabled.  "
 							  "Prepared transactions will be replicated at COMMIT PREPARED.");
+		pg_log_warning_hint("You can use --enable-two-phase switch to enable two_phase.");
 	}
 
 	/*
@@ -1345,8 +1357,9 @@ create_logical_replication_slot(PGconn *conn, struct LogicalRepInfo *dbinfo)
 	slot_name_esc = PQescapeLiteral(conn, slot_name, strlen(slot_name));
 
 	appendPQExpBuffer(str,
-					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, false, false)",
-					  slot_name_esc);
+					  "SELECT lsn FROM pg_catalog.pg_create_logical_replication_slot(%s, 'pgoutput', false, %s, false)",
+					  slot_name_esc,
+					  dbinfos.two_phase ? "true" : "false");
 
 	PQfreemem(slot_name_esc);
 
@@ -1722,8 +1735,9 @@ create_subscription(PGconn *conn, const struct LogicalRepInfo *dbinfo)
 	appendPQExpBuffer(str,
 					  "CREATE SUBSCRIPTION %s CONNECTION %s PUBLICATION %s "
 					  "WITH (create_slot = false, enabled = false, "
-					  "slot_name = %s, copy_data = false)",
-					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc);
+					  "slot_name = %s, copy_data = false, two_phase = %s)",
+					  subname_esc, pubconninfo_esc, pubname_esc, replslotname_esc,
+					  dbinfos.two_phase ? "true" : "false");
 
 	PQfreemem(pubname_esc);
 	PQfreemem(subname_esc);
@@ -1895,6 +1909,7 @@ main(int argc, char **argv)
 		{"publisher-server", required_argument, NULL, 'P'},
 		{"socketdir", required_argument, NULL, 's'},
 		{"recovery-timeout", required_argument, NULL, 't'},
+		{"enable-two-phase", no_argument, NULL, 'T'},
 		{"subscriber-username", required_argument, NULL, 'U'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"version", no_argument, NULL, 'V'},
@@ -1950,6 +1965,7 @@ main(int argc, char **argv)
 	opt.socket_dir = NULL;
 	opt.sub_port = DEFAULT_SUB_PORT;
 	opt.sub_username = NULL;
+	opt.two_phase = false;
 	opt.database_names = (SimpleStringList)
 	{
 		0
@@ -1972,7 +1988,7 @@ main(int argc, char **argv)
 
 	get_restricted_token();
 
-	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:U:v",
+	while ((c = getopt_long(argc, argv, "d:D:np:P:s:t:TU:v",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -2009,6 +2025,9 @@ main(int argc, char **argv)
 			case 't':
 				opt.recovery_timeout = atoi(optarg);
 				break;
+			case 'T':
+				opt.two_phase = true;
+				break;
 			case 'U':
 				opt.sub_username = pg_strdup(optarg);
 				break;
@@ -2170,12 +2189,14 @@ main(int argc, char **argv)
 	/* Rudimentary check for a data directory */
 	check_data_directory(subscriber_dir);
 
+	dbinfos.two_phase = opt.two_phase;
+
 	/*
 	 * Store database information for publisher and subscriber. It should be
 	 * called before atexit() because its return is used in the
 	 * cleanup_objects_atexit().
 	 */
-	dbinfo = store_pub_sub_info(&opt, pub_base_conninfo, sub_base_conninfo);
+	dbinfos.dbinfo = store_pub_sub_info(&opt, pub_base_conninfo, sub_base_conninfo);
 
 	/* Register a function to clean up objects in case of failure */
 	atexit(cleanup_objects_atexit);
@@ -2184,7 +2205,7 @@ main(int argc, char **argv)
 	 * Check if the subscriber data directory has the same system identifier
 	 * than the publisher data directory.
 	 */
-	pub_sysid = get_primary_sysid(dbinfo[0].pubconninfo);
+	pub_sysid = get_primary_sysid(dbinfos.dbinfo[0].pubconninfo);
 	sub_sysid = get_standby_sysid(subscriber_dir);
 	if (pub_sysid != sub_sysid)
 		pg_fatal("subscriber data directory is not a copy of the source database cluster");
@@ -2214,10 +2235,10 @@ main(int argc, char **argv)
 	start_standby_server(&opt, true, false);
 
 	/* Check if the standby server is ready for logical replication */
-	check_subscriber(dbinfo);
+	check_subscriber(dbinfos.dbinfo);
 
 	/* Check if the primary server is ready for logical replication */
-	check_publisher(dbinfo);
+	check_publisher(dbinfos.dbinfo);
 
 	/*
 	 * Stop the target server. The recovery process requires that the server
@@ -2230,10 +2251,10 @@ main(int argc, char **argv)
 	stop_standby_server(subscriber_dir);
 
 	/* Create the required objects for each database on publisher */
-	consistent_lsn = setup_publisher(dbinfo);
+	consistent_lsn = setup_publisher(dbinfos.dbinfo);
 
 	/* Write the required recovery parameters */
-	setup_recovery(dbinfo, subscriber_dir, consistent_lsn);
+	setup_recovery(dbinfos.dbinfo, subscriber_dir, consistent_lsn);
 
 	/*
 	 * Start subscriber so the recovery parameters will take effect. Wait
@@ -2244,7 +2265,7 @@ main(int argc, char **argv)
 	start_standby_server(&opt, true, true);
 
 	/* Waiting the subscriber to be promoted */
-	wait_for_end_recovery(dbinfo[0].subconninfo, &opt);
+	wait_for_end_recovery(dbinfos.dbinfo[0].subconninfo, &opt);
 
 	/*
 	 * Create the subscription for each database on subscriber. It does not
@@ -2252,13 +2273,13 @@ main(int argc, char **argv)
 	 * point to the LSN reported by setup_publisher().  It also cleans up
 	 * publications created by this tool and replication to the standby.
 	 */
-	setup_subscriber(dbinfo, consistent_lsn);
+	setup_subscriber(dbinfos.dbinfo, consistent_lsn);
 
 	/* Remove primary_slot_name if it exists on primary */
-	drop_primary_replication_slot(dbinfo, primary_slot_name);
+	drop_primary_replication_slot(dbinfos.dbinfo, primary_slot_name);
 
 	/* Remove failover replication slots if they exist on subscriber */
-	drop_failover_replication_slots(dbinfo);
+	drop_failover_replication_slots(dbinfos.dbinfo);
 
 	/* Stop the subscriber */
 	pg_log_info("stopping the subscriber");
diff --git a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
index c8dbdb7e9b..c35fa108ce 100644
--- a/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
+++ b/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl
@@ -373,6 +373,7 @@ command_ok(
 
 # Run pg_createsubscriber on node S.  --verbose is used twice
 # to show more information.
+# In passing, also test the --enable-two-phase option
 command_ok(
 	[
 		'pg_createsubscriber',
@@ -388,6 +389,7 @@ command_ok(
 		'--replication-slot' => 'replslot2',
 		'--database' => $db1,
 		'--database' => $db2,
+		'--enable-two-phase'
 	],
 	'run pg_createsubscriber on node S');
 
@@ -406,6 +408,15 @@ $node_p->safe_psql($db2, "INSERT INTO tbl2 VALUES('row 1')");
 # Start subscriber
 $node_s->start;
 
+# Verify that all subtwophase states are pending or enabled,
+# e.g. there are no subscriptions where subtwophase is disabled ('d')
+is( $node_s->safe_psql(
+		'postgres',
+		"SELECT count(1) = 0 FROM pg_subscription WHERE subtwophasestate = 'd'"
+	),
+	't',
+	'subscriptions are created with the two-phase option enabled');
+
 # Confirm the pre-existing subscription has been removed
 $result = $node_s->safe_psql(
 	'postgres', qq(
-- 
2.43.0

#52Amit Kapila
amit.kapila16@gmail.com
In reply to: Shubham Khanna (#51)
Re: Adding a '--two-phase' option to 'pg_createsubscriber' utility.

On Mon, Feb 24, 2025 at 2:51 PM Shubham Khanna
<khannashubham1197@gmail.com> wrote:

The attached patch contains the suggested changes.

Pushed with minor changes. In the latest patch, you have incorrectly
copied one of the checks from the previous version patch which I have
fixed before pushing the patch.

--
With Regards,
Amit Kapila.