diff --git a/dbutils.c b/dbutils.c index abc3610a..a089523b 100644 --- a/dbutils.c +++ b/dbutils.c @@ -4946,6 +4946,7 @@ is_downstream_node_attached(PGconn *conn, char *node_name) return true; } + void set_primary_last_seen(PGconn *conn) { @@ -5000,6 +5001,55 @@ get_primary_last_seen(PGconn *conn) } +bool +is_wal_replay_paused(PGconn *conn, bool check_pending_wal) +{ + PQExpBufferData query; + PGresult *res = NULL; + bool is_paused = false; + + initPQExpBuffer(&query); + + if (PQserverVersion(conn) >= 100000) + { + appendPQExpBufferStr(&query, + "SELECT pg_catalog.pg_is_wal_replay_paused()"); + + if (check_pending_wal == true) + { + appendPQExpBufferStr(&query, + " AND pg_catalog.pg_last_wal_replay_location() < pg_catalog.pg_last_wal_receive_location()"); + } + } + else + { + appendPQExpBufferStr(&query, + "SELECT pg_catalog.pg_is_xlog_replay_paused()"); + if (check_pending_wal == true) + { + appendPQExpBufferStr(&query, + " AND pg_catalog.pg_last_xlog_replay_location() < pg_catalog.pg_last_xlog_receive_location()"); + } + } + + res = PQexec(conn, query.data); + + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + log_db_error(conn, query.data, _("unable to execute \"%s\""), query.data); + } + else + { + is_paused = atobool(PQgetvalue(res, 0, 0)); + } + + termPQExpBuffer(&query); + PQclear(res); + + return is_paused; +} + + /* ============= */ /* BDR functions */ /* ============= */ diff --git a/dbutils.h b/dbutils.h index a0fa3f54..568ba9fa 100644 --- a/dbutils.h +++ b/dbutils.h @@ -548,6 +548,7 @@ void get_node_replication_stats(PGconn *conn, t_node_info *node_info); bool is_downstream_node_attached(PGconn *conn, char *node_name); void set_primary_last_seen(PGconn *conn); int get_primary_last_seen(PGconn *conn); +bool is_wal_replay_paused(PGconn *conn, bool check_pending_wal); /* BDR functions */ int get_bdr_version_num(void); diff --git a/doc/repmgr-standby-promote.sgml b/doc/repmgr-standby-promote.sgml index b0598b5b..1bd968c5 100644 --- a/doc/repmgr-standby-promote.sgml +++ b/doc/repmgr-standby-promote.sgml @@ -33,21 +33,21 @@ Both values can be defined in repmgr.conf. + + + If WAL replay is paused on the standby, and not all WAL files on the standby have been + replayed, &repmgr; will not attempt to promote it. + + + This is because if WAL replay is paused, PostgreSQL itself will not react to a promote command + until WAL replay is resumed and all pending WAL has been replayed. This means + attempting to promote PostgreSQL in this state will leave PostgreSQL in a condition where the + promotion may occur at a unpredictable point in the future. + + + - - Options - - - - - - Check if this node can be promoted, but don't carry out the promotion - - - - - Example @@ -64,6 +64,127 @@ + + Options + + + + + + Check if this node can be promoted, but don't carry out the promotion + + + + + + + + Configuration file settings + + The following parameters in repmgr.conf are relevant to the + promote operation: + + + + + + + + promote_check_interval + with "repmgr standby promote " + + + promote_check_interval: + interval (in seconds, default: 1 seconds) to wait between each check + to determine whether the standby has been promoted. + + + + + + promote_check_timeout + with "repmgr standby promote " + + + promote_check_timeout: + time (in seconds, default: 60 seconds) to wait to verify that the standby has been promoted + before exiting with ERR_PROMOTION_FAIL. + + + + + + + + + + Exit codes + + Following exit codes can be emitted by repmgr standby promote: + + + + + + + The standby was successfully promoted to primary. + + + + + + + + + &repmgr; was unable to connect to the local PostgreSQL node. + + + PostgreSQL must be running before the node can be promoted. + + + + + + + + + The node could not be promoted to primary for one of the following + reasons: + + + + + there is an existing primary node in the replication cluster + + + + + + the node is not a standby + + + + + + WAL replay is paused on the node + + + + + + execution of the PostgreSQL promote command failed + + + + + + + + + + + + Event notifications diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c index 4763987c..c58a03bf 100644 --- a/repmgr-action-standby.c +++ b/repmgr-action-standby.c @@ -1974,7 +1974,7 @@ do_standby_promote(void) { log_error(_("STANDBY PROMOTE can only be executed on a standby node")); PQfinish(conn); - exit(ERR_BAD_CONFIG); + exit(ERR_PROMOTION_FAIL); } else { @@ -1988,6 +1988,38 @@ do_standby_promote(void) log_info(_("node is a standby")); } + /* + * Executing "pg_ctl ... promote" when WAL replay is paused and + * WAL is pending replay will mean the standby will not promote + * until replay is resumed. + * + * As that could happen at any time outside repmgr's control, we + * need to avoid leaving a "ticking timebomb" which might cause + * an unexpected status change in the replication cluster. + */ + if (is_wal_replay_paused(conn, true) == true) + { + ReplInfo replication_info; + + init_replication_info(&replication_info); + + log_error(_("WAL replay is paused on this node but not all WAL has been replayed")); + + if (get_replication_info(conn, &replication_info) == true) + { + log_detail(_("replay paused at %X/%X; last WAL received is %X/%X"), + format_lsn(replication_info.last_wal_replay_lsn), + format_lsn(replication_info.last_wal_receive_lsn)); + } + + if (PQserverVersion(conn) >= 100000) + log_hint(_("execute \"pg_wal_replay_resume()\" to unpause WAL replay")); + else + log_hint(_("execute \"pg_xlog_replay_resume()\" to unpause WAL replay")); + PQfinish(conn); + exit(ERR_PROMOTION_FAIL); + } + /* check that there's no existing primary */ current_primary_conn = get_primary_connection_quiet(conn, &existing_primary_id, NULL);