Allow to enable failover property for replication slots via SQL API.
authorAmit Kapila <[email protected]>
Thu, 25 Jan 2024 06:45:46 +0000 (12:15 +0530)
committerAmit Kapila <[email protected]>
Thu, 25 Jan 2024 06:45:46 +0000 (12:15 +0530)
This commit adds the failover property to the replication slot. The
failover property indicates whether the slot will be synced to the standby
servers, enabling the resumption of corresponding logical replication
after failover. But note that this commit does not yet include the
capability to sync the replication slot; the subsequent commits will add
that capability.

A new optional parameter 'failover' is added to the
pg_create_logical_replication_slot() function. We will also enable to set
'failover' option for slots via the subscription commands in the
subsequent commits.

The value of the 'failover' flag is displayed as part of
pg_replication_slots view.

Author: Hou Zhijie, Shveta Malik, Ajin Cherian
Reviewed-by: Peter Smith, Bertrand Drouvot, Dilip Kumar, Masahiko Sawada, Nisha Moond, Kuroda, Hayato, Amit Kapila
Discussion: https://postgr.es/m/514f6f2f-6833-4539-39f1-96cd1e011f23@enterprisedb.com

16 files changed:
contrib/test_decoding/expected/slot.out
contrib/test_decoding/sql/slot.sql
doc/src/sgml/func.sgml
doc/src/sgml/system-views.sgml
src/backend/catalog/system_functions.sql
src/backend/catalog/system_views.sql
src/backend/replication/slot.c
src/backend/replication/slotfuncs.c
src/backend/replication/walsender.c
src/bin/pg_upgrade/info.c
src/bin/pg_upgrade/pg_upgrade.c
src/bin/pg_upgrade/pg_upgrade.h
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/replication/slot.h
src/test/regress/expected/rules.out

index 63a9940f73abe4935ac333ff16746e4d0ab536d4..261d8886d3b8eefae0a5bbebf465ee30f530b98e 100644 (file)
@@ -406,3 +406,61 @@ SELECT pg_drop_replication_slot('copied_slot2_notemp');
  
 (1 row)
 
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+ ?column? 
+----------
+ init
+(1 row)
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+       slot_name       | slot_type | failover 
+-----------------------+-----------+----------
+ failover_true_slot    | logical   | t
+ failover_false_slot   | logical   | f
+ failover_default_slot | logical   | f
+ physical_slot         | physical  | f
+(4 rows)
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+ pg_drop_replication_slot 
+--------------------------
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_false_slot');
+ pg_drop_replication_slot 
+--------------------------
+(1 row)
+
+SELECT pg_drop_replication_slot('failover_default_slot');
+ pg_drop_replication_slot 
+--------------------------
+(1 row)
+
+SELECT pg_drop_replication_slot('physical_slot');
+ pg_drop_replication_slot 
+--------------------------
+(1 row)
+
index 1aa27c56674b78dfcb456e89519febf340bb94b1..45aeae7fd5a5add7fa51dce13f4ea8e14684f2ee 100644 (file)
@@ -176,3 +176,16 @@ ORDER BY o.slot_name, c.slot_name;
 SELECT pg_drop_replication_slot('orig_slot2');
 SELECT pg_drop_replication_slot('copied_slot2_no_change');
 SELECT pg_drop_replication_slot('copied_slot2_notemp');
+
+-- Test failover option of slots.
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_true_slot', 'test_decoding', false, false, true);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_false_slot', 'test_decoding', false, false, false);
+SELECT 'init' FROM pg_create_logical_replication_slot('failover_default_slot', 'test_decoding', false, false);
+SELECT 'init' FROM pg_create_physical_replication_slot('physical_slot');
+
+SELECT slot_name, slot_type, failover FROM pg_replication_slots;
+
+SELECT pg_drop_replication_slot('failover_true_slot');
+SELECT pg_drop_replication_slot('failover_false_slot');
+SELECT pg_drop_replication_slot('failover_default_slot');
+SELECT pg_drop_replication_slot('physical_slot');
index 5030a1045f9ff6f2563470bc607766350851b898..968e8d59fb5b857ba00a4faa5e1bdebf0f06becb 100644 (file)
@@ -27707,7 +27707,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         <indexterm>
          <primary>pg_create_logical_replication_slot</primary>
         </indexterm>
-        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type> </optional> )
+        <function>pg_create_logical_replication_slot</function> ( <parameter>slot_name</parameter> <type>name</type>, <parameter>plugin</parameter> <type>name</type> <optional>, <parameter>temporary</parameter> <type>boolean</type>, <parameter>twophase</parameter> <type>boolean</type>, <parameter>failover</parameter> <type>boolean</type> </optional> )
         <returnvalue>record</returnvalue>
         ( <parameter>slot_name</parameter> <type>name</type>,
         <parameter>lsn</parameter> <type>pg_lsn</type> )
@@ -27722,8 +27722,13 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
         released upon any error. The optional fourth parameter,
         <parameter>twophase</parameter>, when set to true, specifies
         that the decoding of prepared transactions is enabled for this
-        slot. A call to this function has the same effect as the replication
-        protocol command <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
+        slot. The optional fifth parameter,
+        <parameter>failover</parameter>, when set to true,
+        specifies that this slot is enabled to be synced to the
+        standbys so that logical replication can be resumed after
+        failover. A call to this function has the same effect as
+        the replication protocol command
+        <literal>CREATE_REPLICATION_SLOT ... LOGICAL</literal>.
        </para></entry>
       </row>
 
index 72d01fc624caa5f30acae6400918ab091ce6f785..dd468b31ea775d37a2ea8a6db0a6ad6b5a8f78d4 100644 (file)
@@ -2555,6 +2555,16 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
        </itemizedlist>
       </para></entry>
      </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>failover</structfield> <type>bool</type>
+      </para>
+      <para>
+       True if this is a logical slot enabled to be synced to the standbys.
+       Always false for physical slots.
+      </para></entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
index f315fecf18653fd95ca06cfed39329ada0e08824..346cfb98a04a4c7e69242e2b54c2b00693b2031e 100644 (file)
@@ -479,6 +479,7 @@ CREATE OR REPLACE FUNCTION pg_create_logical_replication_slot(
     IN slot_name name, IN plugin name,
     IN temporary boolean DEFAULT false,
     IN twophase boolean DEFAULT false,
+    IN failover boolean DEFAULT false,
     OUT slot_name name, OUT lsn pg_lsn)
 RETURNS RECORD
 LANGUAGE INTERNAL
index 6288270e2b2bf6d8281fe2c625202b7c44a0c02b..c62aa0074a314b6e2b91d33dde5a096c22b5953a 100644 (file)
@@ -1023,7 +1023,8 @@ CREATE VIEW pg_replication_slots AS
             L.wal_status,
             L.safe_wal_size,
             L.two_phase,
-            L.conflict_reason
+            L.conflict_reason,
+            L.failover
     FROM pg_get_replication_slots() AS L
             LEFT JOIN pg_database D ON (L.datoid = D.oid);
 
index 52da694c7911e30580aae35640e7d67bb1897b0b..02a14ec210ea83b9e6cfb169510c03cbcfb1e901 100644 (file)
@@ -90,7 +90,7 @@ typedef struct ReplicationSlotOnDisk
    sizeof(ReplicationSlotOnDisk) - ReplicationSlotOnDiskConstantSize
 
 #define SLOT_MAGIC     0x1051CA1   /* format identifier */
-#define SLOT_VERSION   3       /* version for new files */
+#define SLOT_VERSION   4       /* version for new files */
 
 /* Control array for replication slot management */
 ReplicationSlotCtlData *ReplicationSlotCtl = NULL;
@@ -248,10 +248,13 @@ ReplicationSlotValidateName(const char *name, int elevel)
  *     during getting changes, if the two_phase option is enabled it can skip
  *     prepare because by that time start decoding point has been moved. So the
  *     user will only get commit prepared.
+ * failover: If enabled, allows the slot to be synced to standbys so
+ *     that logical replication can be resumed after failover.
  */
 void
 ReplicationSlotCreate(const char *name, bool db_specific,
-                     ReplicationSlotPersistency persistency, bool two_phase)
+                     ReplicationSlotPersistency persistency,
+                     bool two_phase, bool failover)
 {
    ReplicationSlot *slot = NULL;
    int         i;
@@ -311,6 +314,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
    slot->data.persistency = persistency;
    slot->data.two_phase = two_phase;
    slot->data.two_phase_at = InvalidXLogRecPtr;
+   slot->data.failover = failover;
 
    /* and then data only present in shared memory */
    slot->just_dirtied = false;
index cad35dce7fc68e84ce0d46293784cac79cb303ca..eb685089b36e3e274ab5422547b81e01e67a6a6c 100644 (file)
@@ -42,7 +42,8 @@ create_physical_replication_slot(char *name, bool immediately_reserve,
 
    /* acquire replication slot, this will check for conflicting names */
    ReplicationSlotCreate(name, false,
-                         temporary ? RS_TEMPORARY : RS_PERSISTENT, false);
+                         temporary ? RS_TEMPORARY : RS_PERSISTENT, false,
+                         false);
 
    if (immediately_reserve)
    {
@@ -117,6 +118,7 @@ pg_create_physical_replication_slot(PG_FUNCTION_ARGS)
 static void
 create_logical_replication_slot(char *name, char *plugin,
                                bool temporary, bool two_phase,
+                               bool failover,
                                XLogRecPtr restart_lsn,
                                bool find_startpoint)
 {
@@ -133,7 +135,8 @@ create_logical_replication_slot(char *name, char *plugin,
     * error as well.
     */
    ReplicationSlotCreate(name, true,
-                         temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase);
+                         temporary ? RS_TEMPORARY : RS_EPHEMERAL, two_phase,
+                         failover);
 
    /*
     * Create logical decoding context to find start point or, if we don't
@@ -171,6 +174,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
    Name        plugin = PG_GETARG_NAME(1);
    bool        temporary = PG_GETARG_BOOL(2);
    bool        two_phase = PG_GETARG_BOOL(3);
+   bool        failover = PG_GETARG_BOOL(4);
    Datum       result;
    TupleDesc   tupdesc;
    HeapTuple   tuple;
@@ -188,6 +192,7 @@ pg_create_logical_replication_slot(PG_FUNCTION_ARGS)
                                    NameStr(*plugin),
                                    temporary,
                                    two_phase,
+                                   failover,
                                    InvalidXLogRecPtr,
                                    true);
 
@@ -232,7 +237,7 @@ pg_drop_replication_slot(PG_FUNCTION_ARGS)
 Datum
 pg_get_replication_slots(PG_FUNCTION_ARGS)
 {
-#define PG_GET_REPLICATION_SLOTS_COLS 15
+#define PG_GET_REPLICATION_SLOTS_COLS 16
    ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
    XLogRecPtr  currlsn;
    int         slotno;
@@ -426,6 +431,8 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
            }
        }
 
+       values[i++] = BoolGetDatum(slot_contents.data.failover);
+
        Assert(i == PG_GET_REPLICATION_SLOTS_COLS);
 
        tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
@@ -693,6 +700,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
    XLogRecPtr  src_restart_lsn;
    bool        src_islogical;
    bool        temporary;
+   bool        failover;
    char       *plugin;
    Datum       values[2];
    bool        nulls[2];
@@ -748,6 +756,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
    src_islogical = SlotIsLogical(&first_slot_contents);
    src_restart_lsn = first_slot_contents.data.restart_lsn;
    temporary = (first_slot_contents.data.persistency == RS_TEMPORARY);
+   failover = first_slot_contents.data.failover;
    plugin = logical_slot ? NameStr(first_slot_contents.data.plugin) : NULL;
 
    /* Check type of replication slot */
@@ -787,6 +796,7 @@ copy_replication_slot(FunctionCallInfo fcinfo, bool logical_slot)
                                        plugin,
                                        temporary,
                                        false,
+                                       failover,
                                        src_restart_lsn,
                                        false);
    }
index 087031e9dc2ec32a411fae8e6cd9741e548b81f8..aa80f3de20f5107d8060c0951f48d7cc6070aae2 100644 (file)
@@ -1212,7 +1212,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
    {
        ReplicationSlotCreate(cmd->slotname, false,
                              cmd->temporary ? RS_TEMPORARY : RS_PERSISTENT,
-                             false);
+                             false, false);
 
        if (reserve_wal)
        {
@@ -1243,7 +1243,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
         */
        ReplicationSlotCreate(cmd->slotname, true,
                              cmd->temporary ? RS_TEMPORARY : RS_EPHEMERAL,
-                             two_phase);
+                             two_phase, false);
 
        /*
         * Do options check early so that we can bail before calling the
index 74e02b3f826510f9a02d70f29bd3c245fb2c1086..183c2f84eb45ce7fd3ddc9dbe68fe0485f20a3b9 100644 (file)
@@ -666,7 +666,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
     * started and stopped several times causing any temporary slots to be
     * removed.
     */
-   res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, "
+   res = executeQueryOrDie(conn, "SELECT slot_name, plugin, two_phase, failover, "
                            "%s as caught_up, conflict_reason IS NOT NULL as invalid "
                            "FROM pg_catalog.pg_replication_slots "
                            "WHERE slot_type = 'logical' AND "
@@ -684,6 +684,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
        int         i_slotname;
        int         i_plugin;
        int         i_twophase;
+       int         i_failover;
        int         i_caught_up;
        int         i_invalid;
 
@@ -692,6 +693,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
        i_slotname = PQfnumber(res, "slot_name");
        i_plugin = PQfnumber(res, "plugin");
        i_twophase = PQfnumber(res, "two_phase");
+       i_failover = PQfnumber(res, "failover");
        i_caught_up = PQfnumber(res, "caught_up");
        i_invalid = PQfnumber(res, "invalid");
 
@@ -702,6 +704,7 @@ get_old_cluster_logical_slot_infos(DbInfo *dbinfo, bool live_check)
            curr->slotname = pg_strdup(PQgetvalue(res, slotnum, i_slotname));
            curr->plugin = pg_strdup(PQgetvalue(res, slotnum, i_plugin));
            curr->two_phase = (strcmp(PQgetvalue(res, slotnum, i_twophase), "t") == 0);
+           curr->failover = (strcmp(PQgetvalue(res, slotnum, i_failover), "t") == 0);
            curr->caught_up = (strcmp(PQgetvalue(res, slotnum, i_caught_up), "t") == 0);
            curr->invalid = (strcmp(PQgetvalue(res, slotnum, i_invalid), "t") == 0);
        }
index 14a36f0503d948738c85027add2f40e8a52cf3f1..10c94a6c1fc82ce07756768ea6772cbb9a58232d 100644 (file)
@@ -916,8 +916,10 @@ create_logical_replication_slots(void)
            appendStringLiteralConn(query, slot_info->slotname, conn);
            appendPQExpBuffer(query, ", ");
            appendStringLiteralConn(query, slot_info->plugin, conn);
-           appendPQExpBuffer(query, ", false, %s);",
-                             slot_info->two_phase ? "true" : "false");
+
+           appendPQExpBuffer(query, ", false, %s, %s);",
+                             slot_info->two_phase ? "true" : "false",
+                             slot_info->failover ? "true" : "false");
 
            PQclear(executeQueryOrDie(conn, "%s", query->data));
 
index a1d08c3dab104773dab1505fae642b52687f3276..d9a848cbfde6cee0fbe615300237779fb6a581bb 100644 (file)
@@ -160,6 +160,8 @@ typedef struct
    bool        two_phase;      /* can the slot decode 2PC? */
    bool        caught_up;      /* has the slot caught up to latest changes? */
    bool        invalid;        /* if true, the slot is unusable */
+   bool        failover;       /* is the slot designated to be synced to the
+                                * physical standby? */
 } LogicalSlotInfo;
 
 typedef struct
index 23944db9e6b1f055235a88a9c815e5a957f74ff3..739f9253bfe8a191fd1d1bc66a97865b48cf0a89 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202401251
+#define CATALOG_VERSION_NO 202401252
 
 #endif
index e4115cd0840f00819a0b59b48c758a48a7070a4e..29af4ce65d5c77f64547262939800d03e91c712d 100644 (file)
   proname => 'pg_get_replication_slots', prorows => '10', proisstrict => 'f',
   proretset => 't', provolatile => 's', prorettype => 'record',
   proargtypes => '',
-  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,text}',
-  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,safe_wal_size,two_phase,conflict_reason}',
+  proallargtypes => '{name,name,text,oid,bool,bool,int4,xid,xid,pg_lsn,pg_lsn,text,int8,bool,text,bool}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn,wal_status,safe_wal_size,two_phase,conflict_reason,failover}',
   prosrc => 'pg_get_replication_slots' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record',
-  proargtypes => 'name name bool bool',
-  proallargtypes => '{name,name,bool,bool,name,pg_lsn}',
-  proargmodes => '{i,i,i,i,o,o}',
-  proargnames => '{slot_name,plugin,temporary,twophase,slot_name,lsn}',
+  proargtypes => 'name name bool bool bool',
+  proallargtypes => '{name,name,bool,bool,bool,name,pg_lsn}',
+  proargmodes => '{i,i,i,i,i,o,o}',
+  proargnames => '{slot_name,plugin,temporary,twophase,failover,slot_name,lsn}',
   prosrc => 'pg_create_logical_replication_slot' },
 { oid => '4222',
   descr => 'copy a logical replication slot, changing temporality and plugin',
index 9e39aaf3037c9d65f95f909f35414a148e05775c..db9bb222661cec483a1dfa17715f3983aa9b6d8e 100644 (file)
@@ -111,6 +111,12 @@ typedef struct ReplicationSlotPersistentData
 
    /* plugin name */
    NameData    plugin;
+
+   /*
+    * Is this a failover slot (sync candidate for standbys)? Only relevant
+    * for logical slots on the primary server.
+    */
+   bool        failover;
 } ReplicationSlotPersistentData;
 
 /*
@@ -218,7 +224,7 @@ extern void ReplicationSlotsShmemInit(void);
 /* management of individual slots */
 extern void ReplicationSlotCreate(const char *name, bool db_specific,
                                  ReplicationSlotPersistency persistency,
-                                 bool two_phase);
+                                 bool two_phase, bool failover);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
 
index 5e846b01e68f5a872435fb9f2278a249b8fe4867..abc944e8b825b81a286c008cf7054cb031f80b4c 100644 (file)
@@ -1473,8 +1473,9 @@ pg_replication_slots| SELECT l.slot_name,
     l.wal_status,
     l.safe_wal_size,
     l.two_phase,
-    l.conflict_reason
-   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size, two_phase, conflict_reason)
+    l.conflict_reason,
+    l.failover
+   FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size, two_phase, conflict_reason, failover)
      LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
 pg_roles| SELECT pg_authid.rolname,
     pg_authid.rolsuper,