]> granicus.if.org Git - postgresql/commitdiff
Add pg_promote function
authorMichael Paquier <michael@paquier.xyz>
Thu, 25 Oct 2018 00:46:00 +0000 (09:46 +0900)
committerMichael Paquier <michael@paquier.xyz>
Thu, 25 Oct 2018 00:46:00 +0000 (09:46 +0900)
This function is able to promote a standby with this new SQL-callable
function.  Execution access can be granted to non-superusers so that
failover tools can observe the principle of least privilege.

Catalog version is bumped.

Author: Laurenz Albe
Reviewed-by: Michael Paquier, Masahiko Sawada
Discussion: https://postgr.es/m/6e7c79b3ec916cf49742fb8849ed17cd87aed620.camel@cybertec.at

13 files changed:
doc/src/sgml/func.sgml
doc/src/sgml/high-availability.sgml
doc/src/sgml/monitoring.sgml
doc/src/sgml/recovery-config.sgml
src/backend/access/transam/xlog.c
src/backend/access/transam/xlogfuncs.c
src/backend/catalog/system_views.sql
src/backend/postmaster/pgstat.c
src/include/access/xlog.h
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/include/pgstat.h
src/test/recovery/t/004_timeline_switch.pl

index 5193df33666c3537be2b07d188e1e22baeb4460a..96d45419e57f2988a3e91cd9935688c0292c0d7c 100644 (file)
@@ -19202,6 +19202,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
    <indexterm>
     <primary>pg_is_wal_replay_paused</primary>
    </indexterm>
+   <indexterm>
+    <primary>pg_promote</primary>
+   </indexterm>
    <indexterm>
     <primary>pg_wal_replay_pause</primary>
    </indexterm>
@@ -19232,6 +19235,22 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        <entry>True if recovery is paused.
        </entry>
       </row>
+      <row>
+       <entry>
+        <literal><function>pg_promote(<parameter>wait</parameter> <type>boolean</type> DEFAULT true, <parameter>wait_seconds</parameter> <type>integer</type> DEFAULT 60)</function></literal>
+        </entry>
+       <entry><type>boolean</type></entry>
+       <entry>
+        Promotes a physical standby server.  Returns <literal>true</literal>
+        if promotion is successful and <literal>false</literal> otherwise.
+        With <parameter>wait</parameter> set to <literal>true</literal>, the
+        default, the function waits until promotion is completed or
+        <parameter>wait_seconds</parameter> seconds have passed, otherwise the
+        function returns immediately after sending the promotion signal to the
+        postmaster.  This function is restricted to superusers by default, but
+        other users can be granted EXECUTE to run the function.
+       </entry>
+      </row>
       <row>
        <entry>
         <literal><function>pg_wal_replay_pause()</function></literal>
index ebcb3daaed6436efb5155fa0a2cc0e75f713a0b1..faf8e718549806b8242ba497d08bfb7bdbcba249 100644 (file)
@@ -1471,14 +1471,17 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)'
    </para>
 
    <para>
-    To trigger failover of a log-shipping standby server,
-    run <command>pg_ctl promote</command> or create a trigger
-    file with the file name and path specified by the <varname>trigger_file</varname>
-    setting in <filename>recovery.conf</filename>. If you're planning to use
-    <command>pg_ctl promote</command> to fail over, <varname>trigger_file</varname> is
-    not required. If you're setting up the reporting servers that are
-    only used to offload read-only queries from the primary, not for high
-    availability purposes, you don't need to promote it.
+    To trigger failover of a log-shipping standby server, run
+    <command>pg_ctl promote</command>, call <function>pg_promote</function>,
+    or create a trigger file with the file name and path specified by the
+    <varname>trigger_file</varname> setting in
+    <filename>recovery.conf</filename>. If you're planning to use
+    <command>pg_ctl promote</command> or to call
+    <function>pg_promote</function> to fail over,
+    <varname>trigger_file</varname> is not required. If you're
+    setting up the reporting servers that are only used to offload read-only
+    queries from the primary, not for high availability purposes, you don't
+    need to promote it.
    </para>
   </sect1>
 
index d4285ea56add874f3616523c3a0c8823e7e271d2..add71458e267d818e4897aaf68e42c50cdb217a2 100644 (file)
@@ -1268,7 +1268,7 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
          <entry>Waiting in an extension.</entry>
         </row>
         <row>
-         <entry morerows="33"><literal>IPC</literal></entry>
+         <entry morerows="34"><literal>IPC</literal></entry>
          <entry><literal>BgWorkerShutdown</literal></entry>
          <entry>Waiting for background worker to shut down.</entry>
         </row>
@@ -1388,6 +1388,10 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
          <entry><literal>ProcArrayGroupUpdate</literal></entry>
          <entry>Waiting for group leader to clear transaction id at transaction end.</entry>
         </row>
+        <row>
+         <entry><literal>Promote</literal></entry>
+         <entry>Waiting for standby promotion.</entry>
+        </row>
         <row>
          <entry><literal>ReplicationOriginDrop</literal></entry>
          <entry>Waiting for a replication origin to become inactive to be dropped.</entry>
index 92825fdf1920e64d801889cc9c47271962cedc9d..a2bdffda94d058079d9e10b6802675fd22761b2a 100644 (file)
@@ -439,7 +439,8 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"'  # Windows
          <para>
           Specifies a trigger file whose presence ends recovery in the
           standby.  Even if this value is not set, you can still promote
-          the standby using <command>pg_ctl promote</command>.
+          the standby using <command>pg_ctl promote</command> or calling
+          <function>pg_promote</function>.
           This setting has no effect if <varname>standby_mode</varname> is <literal>off</literal>.
          </para>
         </listitem>
index 7375a78ffcfb7cbab73b69ddf985c19a3f67c36c..62fc418893c735b59ff3b69adbc904d171e4a829 100644 (file)
 
 extern uint32 bootstrap_data_checksum_version;
 
-/* File path names (all relative to $PGDATA) */
-#define RECOVERY_COMMAND_FILE  "recovery.conf"
-#define RECOVERY_COMMAND_DONE  "recovery.done"
-#define PROMOTE_SIGNAL_FILE            "promote"
-#define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote"
-
 
 /* User-settable parameters */
 int                    max_wal_size_mb = 1024; /* 1 GB */
index 973174297863feaab062e0c01246b612028f936b..a31adcca5ebf0e1a368a86293164c546a1203459 100644 (file)
@@ -16,6 +16,8 @@
  */
 #include "postgres.h"
 
+#include <unistd.h>
+
 #include "access/htup_details.h"
 #include "access/xlog.h"
 #include "access/xlog_internal.h"
@@ -23,6 +25,7 @@
 #include "catalog/pg_type.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "replication/walreceiver.h"
 #include "storage/smgr.h"
 #include "utils/builtins.h"
@@ -697,3 +700,77 @@ pg_backup_start_time(PG_FUNCTION_ARGS)
 
        PG_RETURN_DATUM(xtime);
 }
+
+/*
+ * Promotes a standby server.
+ *
+ * A result of "true" means that promotion has been completed if "wait" is
+ * "true", or initiated if "wait" is false.
+ */
+Datum
+pg_promote(PG_FUNCTION_ARGS)
+{
+       bool            wait = PG_GETARG_BOOL(0);
+       int                     wait_seconds = PG_GETARG_INT32(1);
+       FILE       *promote_file;
+       int                     i;
+
+       if (!RecoveryInProgress())
+               ereport(ERROR,
+                               (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                errmsg("recovery is not in progress"),
+                                errhint("Recovery control functions can only be executed during recovery.")));
+
+       if (wait_seconds <= 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+                                errmsg("\"wait_seconds\" cannot be negative or equal zero")));
+
+       /* create the promote signal file */
+       promote_file = AllocateFile(PROMOTE_SIGNAL_FILE, "w");
+       if (!promote_file)
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not create file \"%s\": %m",
+                                               PROMOTE_SIGNAL_FILE)));
+
+       if (FreeFile(promote_file))
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not write file \"%s\": %m",
+                                               PROMOTE_SIGNAL_FILE)));
+
+       /* signal the postmaster */
+       if (kill(PostmasterPid, SIGUSR1) != 0)
+       {
+               ereport(WARNING,
+                               (errmsg("failed to send signal to postmaster: %m")));
+               (void) unlink(PROMOTE_SIGNAL_FILE);
+               PG_RETURN_BOOL(false);
+       }
+
+       /* return immediately if waiting was not requested */
+       if (!wait)
+               PG_RETURN_BOOL(true);
+
+       /* wait for the amount of time wanted until promotion */
+#define WAITS_PER_SECOND 10
+       for (i = 0; i < WAITS_PER_SECOND * wait_seconds; i++)
+       {
+               ResetLatch(MyLatch);
+
+               if (!RecoveryInProgress())
+                       PG_RETURN_BOOL(true);
+
+               CHECK_FOR_INTERRUPTS();
+
+               WaitLatch(MyLatch,
+                                 WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+                                 1000L / WAITS_PER_SECOND,
+                                 WAIT_EVENT_PROMOTE);
+       }
+
+       ereport(WARNING,
+                       (errmsg("server did not promote within %d seconds", wait_seconds)));
+       PG_RETURN_BOOL(false);
+}
index a03b005f73e948d872e4046af9c9e3ad1cefb732..53ddc593a8c4d170850f61b27ad54ba7ee36495d 100644 (file)
@@ -1027,6 +1027,11 @@ CREATE OR REPLACE FUNCTION pg_stop_backup (
   RETURNS SETOF record STRICT VOLATILE LANGUAGE internal as 'pg_stop_backup_v2'
   PARALLEL RESTRICTED;
 
+CREATE OR REPLACE FUNCTION
+  pg_promote(wait boolean DEFAULT true, wait_seconds integer DEFAULT 60)
+  RETURNS boolean STRICT VOLATILE LANGUAGE INTERNAL AS 'pg_promote'
+  PARALLEL RESTRICTED;
+
 -- legacy definition for compatibility with 9.3
 CREATE OR REPLACE FUNCTION
   json_populate_record(base anyelement, from_json json, use_json_as_text boolean DEFAULT false)
@@ -1138,6 +1143,7 @@ REVOKE EXECUTE ON FUNCTION pg_rotate_logfile() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_reload_conf() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_current_logfile() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_current_logfile(text) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_promote(boolean, integer) FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_shared(text) FROM public;
index 774f03f570265759a58a0fbd5014f6ce68149ce2..42bccce0af4b2db4564bc14cb391c7dd8122ab2d 100644 (file)
@@ -3663,6 +3663,9 @@ pgstat_get_wait_ipc(WaitEventIPC w)
                case WAIT_EVENT_PROCARRAY_GROUP_UPDATE:
                        event_name = "ProcArrayGroupUpdate";
                        break;
+               case WAIT_EVENT_PROMOTE:
+                       event_name = "Promote";
+                       break;
                case WAIT_EVENT_REPLICATION_ORIGIN_DROP:
                        event_name = "ReplicationOriginDrop";
                        break;
index 421ba6d7755605473333b95bc7ae308766f2b4c7..e01d12eb7c8fc0743e01f35794fefe9ce90a1562 100644 (file)
@@ -319,10 +319,16 @@ extern void do_pg_abort_backup(void);
 extern SessionBackupState get_backup_status(void);
 
 /* File path names (all relative to $PGDATA) */
+#define RECOVERY_COMMAND_FILE  "recovery.conf"
+#define RECOVERY_COMMAND_DONE  "recovery.done"
 #define BACKUP_LABEL_FILE              "backup_label"
 #define BACKUP_LABEL_OLD               "backup_label.old"
 
 #define TABLESPACE_MAP                 "tablespace_map"
 #define TABLESPACE_MAP_OLD             "tablespace_map.old"
 
+/* files to signal promotion to primary */
+#define PROMOTE_SIGNAL_FILE            "promote"
+#define FALLBACK_PROMOTE_SIGNAL_FILE  "fallback_promote"
+
 #endif                                                 /* XLOG_H */
index e04eabf683895c734b50c3db2aeaf75d3559adee..1d5fe83c1ac1dba965588912b1620a5148c50836 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201810111
+#define CATALOG_VERSION_NO     201810251
 
 #endif
index cff58ed2d89b5fc1346a3533cfc557d579535f50..4d7fe1b383faf7ace476372b6e99e743a50d72b2 100644 (file)
   proname => 'pg_backup_start_time', provolatile => 's',
   prorettype => 'timestamptz', proargtypes => '',
   prosrc => 'pg_backup_start_time' },
+{ oid => '3436', descr => 'promote standby server',
+  proname => 'pg_promote', provolatile => 'v',
+  prorettype => 'bool', proargtypes => 'bool int4', proargnames => '{wait,wait_seconds}',
+  prosrc => 'pg_promote' },
 { oid => '2848', descr => 'switch to new wal file',
   proname => 'pg_switch_wal', provolatile => 'v', prorettype => 'pg_lsn',
   proargtypes => '', prosrc => 'pg_switch_wal' },
index fd1d52a0c513e22feab5c0d1ab314f54ef918548..f1c10d16b8b2adfb9efdfb1ed4444ee1b265c6f4 100644 (file)
@@ -829,6 +829,7 @@ typedef enum
        WAIT_EVENT_PARALLEL_CREATE_INDEX_SCAN,
        WAIT_EVENT_PARALLEL_FINISH,
        WAIT_EVENT_PROCARRAY_GROUP_UPDATE,
+       WAIT_EVENT_PROMOTE,
        WAIT_EVENT_REPLICATION_ORIGIN_DROP,
        WAIT_EVENT_REPLICATION_SLOT_DROP,
        WAIT_EVENT_SAFE_SNAPSHOT,
index 34ee3351296fd923f5de92f668061707182a20e7..a7ccb7b4a3bba938a484ecda59ec8ec6e656e791 100644 (file)
@@ -6,7 +6,7 @@ use warnings;
 use File::Path qw(rmtree);
 use PostgresNode;
 use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 $ENV{PGDATABASE} = 'postgres';
 
@@ -37,9 +37,14 @@ $node_master->safe_psql('postgres',
 $node_master->wait_for_catchup($node_standby_1, 'replay',
        $node_master->lsn('write'));
 
-# Stop and remove master, and promote standby 1, switching it to a new timeline
+# Stop and remove master
 $node_master->teardown_node;
-$node_standby_1->promote;
+
+# promote standby 1 using "pg_promote", switching it to a new timeline
+my $psql_out = '';
+$node_standby_1->psql('postgres', "SELECT pg_promote(wait_seconds => 300)",
+       stdout => \$psql_out);
+is($psql_out, 't', "promotion of standby with pg_promote");
 
 # Switch standby 2 to replay from standby 1
 rmtree($node_standby_2->data_dir . '/recovery.conf');