diff options
Diffstat (limited to 'src/backend/commands/subscriptioncmds.c')
-rw-r--r-- | src/backend/commands/subscriptioncmds.c | 131 |
1 files changed, 129 insertions, 2 deletions
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index eb88d877a5..5f834a9c30 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -59,6 +59,7 @@ #define SUBOPT_REFRESH 0x00000040 #define SUBOPT_BINARY 0x00000080 #define SUBOPT_STREAMING 0x00000100 +#define SUBOPT_TWOPHASE_COMMIT 0x00000200 /* check if the 'val' has 'bits' set */ #define IsSet(val, bits) (((val) & (bits)) == (bits)) @@ -79,6 +80,7 @@ typedef struct SubOpts bool refresh; bool binary; bool streaming; + bool twophase; } SubOpts; static List *fetch_table_list(WalReceiverConn *wrconn, List *publications); @@ -123,6 +125,8 @@ parse_subscription_options(List *stmt_options, bits32 supported_opts, SubOpts *o opts->binary = false; if (IsSet(supported_opts, SUBOPT_STREAMING)) opts->streaming = false; + if (IsSet(supported_opts, SUBOPT_TWOPHASE_COMMIT)) + opts->twophase = false; /* Parse options */ foreach(lc, stmt_options) @@ -237,6 +241,29 @@ parse_subscription_options(List *stmt_options, bits32 supported_opts, SubOpts *o opts->specified_opts |= SUBOPT_STREAMING; opts->streaming = defGetBoolean(defel); } + else if (strcmp(defel->defname, "two_phase") == 0) + { + /* + * Do not allow toggling of two_phase option. Doing so could cause + * missing of transactions and lead to an inconsistent replica. + * See comments atop worker.c + * + * Note: Unsupported twophase indicates that this call originated + * from AlterSubscription. + */ + if (!IsSet(supported_opts, SUBOPT_TWOPHASE_COMMIT)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized subscription parameter: \"%s\"", defel->defname))); + + if (IsSet(opts->specified_opts, SUBOPT_TWOPHASE_COMMIT)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + + opts->specified_opts |= SUBOPT_TWOPHASE_COMMIT; + opts->twophase = defGetBoolean(defel); + } else ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -325,6 +352,25 @@ parse_subscription_options(List *stmt_options, bits32 supported_opts, SubOpts *o errmsg("subscription with %s must also set %s", "slot_name = NONE", "create_slot = false"))); } + + /* + * Do additional checking for the disallowed combination of two_phase and + * streaming. While streaming and two_phase can theoretically be + * supported, it needs more analysis to allow them together. + */ + if (opts->twophase && + IsSet(supported_opts, SUBOPT_TWOPHASE_COMMIT) && + IsSet(opts->specified_opts, SUBOPT_TWOPHASE_COMMIT)) + { + if (opts->streaming && + IsSet(supported_opts, SUBOPT_STREAMING) && + IsSet(opts->specified_opts, SUBOPT_STREAMING)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + /*- translator: both %s are strings of the form "option = value" */ + errmsg("%s and %s are mutually exclusive options", + "two_phase = true", "streaming = true"))); + } } /* @@ -385,7 +431,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) supported_opts = (SUBOPT_CONNECT | SUBOPT_ENABLED | SUBOPT_CREATE_SLOT | SUBOPT_SLOT_NAME | SUBOPT_COPY_DATA | SUBOPT_SYNCHRONOUS_COMMIT | SUBOPT_BINARY | - SUBOPT_STREAMING); + SUBOPT_STREAMING | SUBOPT_TWOPHASE_COMMIT); parse_subscription_options(stmt->options, supported_opts, &opts); /* @@ -455,6 +501,10 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(opts.enabled); values[Anum_pg_subscription_subbinary - 1] = BoolGetDatum(opts.binary); values[Anum_pg_subscription_substream - 1] = BoolGetDatum(opts.streaming); + values[Anum_pg_subscription_subtwophasestate - 1] = + CharGetDatum(opts.twophase ? + LOGICALREP_TWOPHASE_STATE_PENDING : + LOGICALREP_TWOPHASE_STATE_DISABLED); values[Anum_pg_subscription_subconninfo - 1] = CStringGetTextDatum(conninfo); if (opts.slot_name) @@ -532,10 +582,35 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel) */ if (opts.create_slot) { + bool twophase_enabled = false; + Assert(opts.slot_name); - walrcv_create_slot(wrconn, opts.slot_name, false, + /* + * Even if two_phase is set, don't create the slot with + * two-phase enabled. Will enable it once all the tables are + * synced and ready. This avoids race-conditions like prepared + * transactions being skipped due to changes not being applied + * due to checks in should_apply_changes_for_rel() when + * tablesync for the corresponding tables are in progress. See + * comments atop worker.c. + * + * Note that if tables were specified but copy_data is false + * then it is safe to enable two_phase up-front because those + * tables are already initially in READY state. When the + * subscription has no tables, we leave the twophase state as + * PENDING, to allow ALTER SUBSCRIPTION ... REFRESH + * PUBLICATION to work. + */ + if (opts.twophase && !opts.copy_data && tables != NIL) + twophase_enabled = true; + + walrcv_create_slot(wrconn, opts.slot_name, false, twophase_enabled, CRS_NOEXPORT_SNAPSHOT, NULL); + + if (twophase_enabled) + UpdateTwoPhaseState(subid, LOGICALREP_TWOPHASE_STATE_ENABLED); + ereport(NOTICE, (errmsg("created replication slot \"%s\" on publisher", opts.slot_name))); @@ -865,6 +940,12 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) if (IsSet(opts.specified_opts, SUBOPT_STREAMING)) { + if ((sub->twophasestate != LOGICALREP_TWOPHASE_STATE_DISABLED) && opts.streaming) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot set %s for two-phase enabled subscription", + "streaming = true"))); + values[Anum_pg_subscription_substream - 1] = BoolGetDatum(opts.streaming); replaces[Anum_pg_subscription_substream - 1] = true; @@ -927,6 +1008,17 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + /* + * See ALTER_SUBSCRIPTION_REFRESH for details why this is + * not allowed. + */ + if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"), + errhint("Use ALTER SUBSCRIPTION ...SET PUBLICATION with refresh = false, or with copy_data = false" + ", or use DROP/CREATE SUBSCRIPTION."))); + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); /* Make sure refresh sees the new list of publications. */ @@ -966,6 +1058,17 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) errmsg("ALTER SUBSCRIPTION with refresh is not allowed for disabled subscriptions"), errhint("Use ALTER SUBSCRIPTION ... SET PUBLICATION ... WITH (refresh = false)."))); + /* + * See ALTER_SUBSCRIPTION_REFRESH for details why this is + * not allowed. + */ + if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION with refresh and copy_data is not allowed when two_phase is enabled"), + errhint("Use ALTER SUBSCRIPTION ...SET PUBLICATION with refresh = false, or with copy_data = false" + ", or use DROP/CREATE SUBSCRIPTION."))); + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION with refresh"); /* Only refresh the added/dropped list of publications. */ @@ -986,6 +1089,30 @@ AlterSubscription(AlterSubscriptionStmt *stmt, bool isTopLevel) parse_subscription_options(stmt->options, SUBOPT_COPY_DATA, &opts); + /* + * The subscription option "two_phase" requires that + * replication has passed the initial table synchronization + * phase before the two_phase becomes properly enabled. + * + * But, having reached this two-phase commit "enabled" state + * we must not allow any subsequent table initialization to + * occur. So the ALTER SUBSCRIPTION ... REFRESH is disallowed + * when the user had requested two_phase = on mode. + * + * The exception to this restriction is when copy_data = + * false, because when copy_data is false the tablesync will + * start already in READY state and will exit directly without + * doing anything. + * + * For more details see comments atop worker.c. + */ + if (sub->twophasestate == LOGICALREP_TWOPHASE_STATE_ENABLED && opts.copy_data) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("ALTER SUBSCRIPTION ... REFRESH with copy_data is not allowed when two_phase is enabled"), + errhint("Use ALTER SUBSCRIPTION ... REFRESH with copy_data = false" + ", or use DROP/CREATE SUBSCRIPTION."))); + PreventInTransactionBlock(isTopLevel, "ALTER SUBSCRIPTION ... REFRESH"); AlterSubscription_refresh(sub, opts.copy_data); |