diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
index 8c24ae2..3071879 100644
--- a/doc/src/sgml/recovery-config.sgml
+++ b/doc/src/sgml/recovery-config.sgml
@@ -11,30 +11,90 @@
This chapter describes the settings available in the
- recovery.conf>recovery.conf>>
- file. They apply only for the duration of the
+ postgresql.conf>postgresql.conf>>
+ file that apply only for the duration of the
recovery. They must be reset for any subsequent recovery you wish to
- perform. They cannot be changed once recovery has begun.
+ perform.
- Settings in recovery.conf> are specified in the format
- name = 'value'>. One parameter is specified per line.
- Hash marks (#) designate the rest of the
- line as a comment. To embed a single quote in a parameter
- value, write two quotes (''>).
+ The database server can also be started "in recovery" a term that covers
+ using the server as a standby or for executing a targeted recovery. Typically
+ standby mode would be used to provide high availability and/or read
+ scalability, whereas a targeted recovery is used to recover from data loss.
- A sample file, share/recovery.conf.sample>,
- is provided in the installation's share/> directory.
+ To start the server in standby mode create a zero-length file
+ called standby.signal>standby.signal>>
+ in the signal_file_directory>. The server will enter recovery and
+ will not stop recovery when the end of archived WAL is reached, but
+ will keep trying to continue recovery by connecting to the sending server as
+ specified by the sender_conninfo> setting and/or by
+ fetching new WAL segments using restore_command>
+ In this mode you may use parameters
+ in both and
+ sections. Parameters from
+ will not be used.
+
+ To start the server in targeted recovery create a zero-length file called
+ recovery.signal>recovery.signal>>
+ in the signal_file_directory>.
+ If both standby.signal> and recovery.signal> files are
+ created, standby mode takes precedence. Targeted recovery mode will end when
+ end of archived WAL is reached, or when recovery_target> is reached.
+ In this mode you may use parameters from both
+ and
+ sections. Parameters from
+ will not be used.
+
+
+
+
+ Recovery Signal Settings
+
+
+
+ signal_file_directory (string)
+
+ signal_file_directory> recovery parameter
+ trigger_file> a now-deprecated recovery parameter used to specify name of a promotion signal file
+
+
+
+
+ Specifies a directory where files can be created to signal state changes
+ to the server, also sometimes described as a trigger file though that term
+ is not normally used. The default setting is data_directory>.
+
+
+ The server will look for standby.signal>, recovery.signal>
+ and promote.signal> files in this directory.
+
+
+ The parameter may only be changed at server start.
+
+
+
+
+
+
+
+
Archive Recovery Settings
+
+ Parameter settings may be changed in postgresql.conf or
+ by executing the ALTER SYSTEM. Changes will take some
+ time to take effect, so changes made while not in paused state may not
+ have the desired effect in all cases.
+
+
restore_command (string)
@@ -45,7 +105,7 @@
The local shell command to execute to retrieve an archived segment of
the WAL file series. This parameter is required for archive recovery,
- but optional for streaming replication.
+ but optional for standby mode.
Any %f> in the string is
replaced by the name of the file to retrieve from the archive,
and any %p> is replaced by the copy destination path name
@@ -154,99 +214,91 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
Recovery Target Settings
- By default, recovery will recover to the end of the WAL log. The
- following parameters can be used to specify an earlier stopping point.
- At most one of recovery_target>,
- recovery_target_lsn>, recovery_target_name>,
- recovery_target_time>, or recovery_target_xid>
- can be used; if more than one of these is specified in the configuration
- file, the last entry will be used.
+ By default, recovery will recover to the end of the WAL log. An earlier
+ stopping point may be specified using recovery_target_type>
+ and in most cases also recovery_target_value>, plus the optional
+ parameters recovery_target_inclusive>,
+ recovery_targeti_timeline> and recovery_target_action>.
+
+
+
+ Parameter settings may be changed in postgresql.conf or
+ by executing the ALTER SYSTEM. Changes will take some
+ time to take effect, so changes made while not in paused state may not
+ have the desired effect in all cases.
-
- recovery_target = 'immediate'
+
+ recovery_target_type (enum)
- recovery_target> recovery parameter
+ recovery_target_type> targeted recovery parameter
- This parameter specifies that recovery should end as soon as a
- consistent state is reached, i.e. as early as possible. When restoring
- from an online backup, this means the point where taking the backup
- ended.
+ recovery_target_type specifies the search criteria used for a
+ a targeted recovery. The default value is none. Valid
+ values are none, immediate,
+ name, timestamp,
+ xid and lsn.
+
- Technically, this is a string parameter, but 'immediate'>
- is currently the only allowed value.
+ Target-type none specifies that recovery will not stop
+ until it runs out of WAL, which is the default setting. When not in targeted
+ recovery this is the only meaningful setting.
-
-
-
- recovery_target_name (string)
-
- recovery_target_name> recovery parameter
-
-
-
- This parameter specifies the named restore point (created with
- pg_create_restore_point()>) to which recovery will proceed.
+ Target-type immediate specifies that recovery should end as
+ soon as a consistent state is reached, i.e. as early as possible. When restoring
+ from an online backup, this means the point where taking the backup ended.
+ For this target-type, no additional target-specifiers influence the stopping
+ point.
-
-
-
- recovery_target_time (timestamp)
-
- recovery_target_time> recovery parameter
-
-
-
- This parameter specifies the time stamp up to which recovery
- will proceed.
+ Target-type name specifies that recovery will
+ proceed to the point specified by recovery_target_value
+ when interpreted as the name of a restore point created with
+ pg_create_restore_point()>.
The precise stopping point is also influenced by
- .
+ and
+ .
-
-
-
- recovery_target_xid (string)
-
- recovery_target_xid> recovery parameter
-
-
-
- This parameter specifies the transaction ID up to which recovery
- will proceed. Keep in mind
+ Target-type timestamp specifies that recovery will
+ proceed to the point specified by recovery_target_value
+ when interpreted as the timestamp of a transaction commit or abort.
+ The precise stopping point is also influenced by
+ and
+ .
+
+
+
+ Target-type xid specifies that recovery will
+ proceed to the point specified by recovery_target_value
+ when interpreted as the transaction ID of a transaction commit or abort.
+ Keep in mind
that while transaction IDs are assigned sequentially at transaction
start, transactions can complete in a different numeric order.
The transactions that will be recovered are those that committed
before (and optionally including) the specified one.
The precise stopping point is also influenced by
- .
+ and
+ .
-
-
-
- recovery_target_lsn (pg_lsn)
-
- recovery_target_lsn> recovery parameter
-
-
-
- This parameter specifies the LSN of the transaction log location up
- to which recovery will proceed. The precise stopping point is also
- influenced by . This
- parameter is parsed using the system data type
- pg_lsn>.
+ Target-type lsn specifies that recovery will
+ proceed to the point specified by recovery_target_value
+ when interpreted as the LSN of any WAL record, parsed using the system
+ data type pg_lsn>.
+ The precise stopping point is also influenced by
+ and
+ .
@@ -258,6 +310,23 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
+
+ recovery_target_value (string)
+
+ recovery_target_value> targeted recovery search parameter
+
+
+
+
+ Specifies the stopping point for targeted recovery. The string value
+ is interpreted according to strict rules according to the value of
+ recovery_target_type. An empty string may be an
+ invalid value in some cases.
+
+
+
+
recovery_target_inclusive (boolean)
@@ -357,34 +426,21 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
Standby Server Settings
-
- standby_mode (boolean)
-
- standby_mode> recovery parameter
-
-
-
-
- Specifies whether to start the PostgreSQL> server as
- a standby. If this parameter is on>, the server will
- not stop recovery when the end of archived WAL is reached, but
- will keep trying to continue recovery by fetching new WAL segments
- using restore_command>
- and/or by connecting to the primary server as specified by the
- primary_conninfo> setting.
-
-
-
-
- primary_conninfo (string)
+
+ Parameter settings may be changed only at server start, though later
+ patches may add this capability.
+
+
+
+ sender_conninfo (string)
- primary_conninfo> recovery parameter
+ sender_conninfo> recovery parameter
Specifies a connection string to be used for the standby server
- to connect with the primary. This string is in the format
+ to connect with a sending server. This string is in the format
described in . If any option is
unspecified in this string, then the corresponding environment
variable (see ) is checked. If the
@@ -393,56 +449,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
The connection string should specify the host name (or address)
- of the primary server, as well as the port number if it is not
+ of the sending server, as well as the port number if it is not
the same as the standby server's default.
Also specify a user name corresponding to a suitably-privileged role
- on the primary (see
+ on the sending server (see
).
- A password needs to be provided too, if the primary demands password
+ A password needs to be provided too, if the sender demands password
authentication. It can be provided in the
- primary_conninfo string, or in a separate
+ sender_conninfo string, or in a separate
~/.pgpass> file on the standby server (use
replication> as the database name).
Do not specify a database name in the
- primary_conninfo string.
+ sender_conninfo string.
This setting has no effect if standby_mode> is off>.
-
- primary_slot_name (string)
+
+
+ sender_slot_name (string)
- primary_slot_name> recovery parameter
+ sender_slot_name> recovery parameter
Optionally specifies an existing replication slot to be used when
- connecting to the primary via streaming replication to control
+ connecting to the sending server via streaming replication to control
resource removal on the upstream node
(see ).
- This setting has no effect if primary_conninfo> is not
+ This setting has no effect if sender_conninfo> is not
set.
-
- trigger_file (string)
-
- trigger_file> recovery parameter
-
-
-
-
- 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 pg_ctl promote>.
- This setting has no effect if standby_mode> is off>.
-
-
- recovery_min_apply_delay (integer)
@@ -453,7 +495,7 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
By default, a standby server restores WAL records from the
- primary as soon as possible. It may be useful to have a time-delayed
+ sending server as soon as possible. It may be useful to have a time-delayed
copy of the data, offering opportunities to correct data loss errors.
This parameter allows you to delay recovery by a fixed period of time,
measured in milliseconds if no unit is specified. For example, if
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f8ffa5c..e4168c2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -76,11 +76,13 @@
extern uint32 bootstrap_data_checksum_version;
-/* File path names (all relative to $PGDATA) */
+/* File path names */
#define RECOVERY_COMMAND_FILE "recovery.conf"
#define RECOVERY_COMMAND_DONE "recovery.done"
-#define PROMOTE_SIGNAL_FILE "promote"
-#define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote"
+#define PROMOTE_SIGNAL_FILE "promote.signal"
+#define FALLBACK_PROMOTE_SIGNAL_FILE "fallback_promote.signal"
+#define RECOVERY_SIGNAL_FILE "recovery.signal"
+#define STANDBY_SIGNAL_FILE "standby.signal"
/* User-settable parameters */
@@ -241,29 +243,35 @@ static int LocalXLogInsertAllowed = -1;
*/
bool ArchiveRecoveryRequested = false;
bool InArchiveRecovery = false;
+static bool standby_signal_file_found = false;
+static bool recovery_signal_file_found = false;
/* Was the last xlog file restored from archive, or local? */
static bool restoredFromArchive = false;
/* options taken from recovery.conf for archive recovery */
-char *recoveryRestoreCommand = NULL;
-static char *recoveryEndCommand = NULL;
-static char *archiveCleanupCommand = NULL;
-static RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
-static bool recoveryTargetInclusive = true;
-static RecoveryTargetAction recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
-static TransactionId recoveryTargetXid;
-static TimestampTz recoveryTargetTime;
-static char *recoveryTargetName;
-static XLogRecPtr recoveryTargetLSN;
-static int recovery_min_apply_delay = 0;
-static TimestampTz recoveryDelayUntilTime;
+char *recoveryRestoreCommand = NULL;
+char *recoveryEndCommand = NULL;
+char *archiveCleanupCommand = NULL;
+char *recoveryTargetTypeString;
+RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
+char *recoveryTargetValue = NULL;
+bool recoveryTargetInclusive = true;
+RecoveryTargetAction recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
+TransactionId recoveryTargetXid;
+TimestampTz recoveryTargetTime;
+char *recoveryTargetName;
+XLogRecPtr recoveryTargetLSN;
+int recovery_min_apply_delay = 0;
+TimestampTz recoveryDelayUntilTime;
/* options taken from recovery.conf for XLOG streaming */
-static bool StandbyModeRequested = false;
-static char *PrimaryConnInfo = NULL;
-static char *PrimarySlotName = NULL;
-static char *TriggerFile = NULL;
+bool StandbyModeRequested = false;
+
+char *signal_file_directory = NULL;
+char PromoteSignalFile[MAXPGPATH];
+char RecoverySignalFile[MAXPGPATH];
+char StandbySignalFile[MAXPGPATH];
/* are we currently in standby mode? */
bool StandbyMode = false;
@@ -289,7 +297,11 @@ static bool recoveryStopAfter;
* the currently-scanned WAL record was generated). We also need these
* timeline values:
*
- * recoveryTargetTLI: the desired timeline that we want to end in.
+ * recoveryTargetTimeLineGoal: what the user requested, if any
+ *
+ * recoveryTargetTLIRequested: numeric value of requested timeline, if constant
+ *
+ * recoveryTargetTLI: the currently understood target timeline; changes
*
* recoveryTargetIsLatest: was the requested target timeline 'latest'?
*
@@ -305,7 +317,10 @@ static bool recoveryStopAfter;
* file was created.) During a sequential scan we do not allow this value
* to decrease.
*/
-static TimeLineID recoveryTargetTLI;
+char *recoveryTargetTLIString = NULL;
+RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
+TimeLineID recoveryTargetTLIRequested = 0;
+TimeLineID recoveryTargetTLI = 0;
static bool recoveryTargetIsLatest = false;
static List *expectedTLEs;
static TimeLineID curFileTLI;
@@ -801,7 +816,8 @@ static bool holdingAllLocks = false;
static MemoryContext walDebugCxt = NULL;
#endif
-static void readRecoveryCommandFile(void);
+static void readRecoverySignalFile(void);
+static void logRecoveryParameters(void);
static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog);
static bool recoveryStopsBefore(XLogReaderState *record);
static bool recoveryStopsAfter(XLogReaderState *record);
@@ -4963,261 +4979,170 @@ str_time(pg_time_t tnow)
/*
* See if there is a recovery command file (recovery.conf), and if so
- * read in parameters for archive recovery and XLOG streaming.
+ * throw an ERROR.
*
- * The file is parsed using the main configuration parser.
+ * See if there are any recovery signal files and if so, set state for
+ * recovery.
*/
static void
-readRecoveryCommandFile(void)
+readRecoverySignalFile(void)
{
- FILE *fd;
- TimeLineID rtli = 0;
- bool rtliGiven = false;
- ConfigVariable *item,
- *head = NULL,
- *tail = NULL;
- bool recoveryTargetActionSet = false;
+ struct stat stat_buf;
+ if (IsBootstrapProcessingMode())
+ return;
- fd = AllocateFile(RECOVERY_COMMAND_FILE, "r");
- if (fd == NULL)
+ /*
+ * Set paths for named signal files
+ */
+ if (signal_file_directory)
{
- if (errno == ENOENT)
- return; /* not there, so no archive recovery */
+ snprintf(StandbySignalFile, MAXPGPATH, "%s/%s", signal_file_directory, STANDBY_SIGNAL_FILE);
+ snprintf(RecoverySignalFile, MAXPGPATH, "%s/%s", signal_file_directory, RECOVERY_SIGNAL_FILE);
+ snprintf(PromoteSignalFile, MAXPGPATH, "%s/%s", signal_file_directory, PROMOTE_SIGNAL_FILE);
+ }
+ else
+ {
+ snprintf(StandbySignalFile, MAXPGPATH, "%s", STANDBY_SIGNAL_FILE);
+ snprintf(RecoverySignalFile, MAXPGPATH, "%s", RECOVERY_SIGNAL_FILE);
+ snprintf(PromoteSignalFile, MAXPGPATH, "%s", PROMOTE_SIGNAL_FILE);
+ }
+
+ /*
+ * Check for old recovery API file: recovery.conf
+ */
+ if (stat(RECOVERY_COMMAND_FILE, &stat_buf) == 0)
ereport(FATAL,
- (errcode_for_file_access(),
- errmsg("could not open recovery command file \"%s\": %m",
+ (errcode_for_file_access(),
+ errmsg("deprecated API using recovery command file \"%s\"",
RECOVERY_COMMAND_FILE)));
+ /*
+ * Remove unused .done file, if present. Ignore if absent.
+ */
+ unlink(RECOVERY_COMMAND_DONE);
+
+ /*
+ * Check for recovery signal files.
+ *
+ * If present, standby signal file takes precedence.
+ * If neither is present then we won't enter archive recovery.
+ */
+ if (stat(StandbySignalFile, &stat_buf) == 0)
+ {
+ standby_signal_file_found = true;
+ StandbyModeRequested = true;
+ ArchiveRecoveryRequested = true;
+ }
+ else if (stat(RecoverySignalFile, &stat_buf) == 0)
+ {
+ recovery_signal_file_found = true;
+ StandbyModeRequested = false;
+ ArchiveRecoveryRequested = true;
}
+ else
+ return;
/*
- * Since we're asking ParseConfigFp() to report errors as FATAL, there's
- * no need to check the return value.
+ * We don't support standby_mode in standalone backends; that requires
+ * other processes such as the WAL receiver to be alive.
*/
- (void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail);
+ if (StandbyModeRequested && !IsUnderPostmaster)
+ ereport(FATAL,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("standby mode is not supported by single-user servers")));
- FreeFile(fd);
+ logRecoveryParameters();
+ validateRecoveryParameters();
+}
- for (item = head; item; item = item->next)
- {
- if (strcmp(item->name, "restore_command") == 0)
- {
- recoveryRestoreCommand = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("restore_command = '%s'",
- recoveryRestoreCommand)));
- }
- else if (strcmp(item->name, "recovery_end_command") == 0)
- {
- recoveryEndCommand = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("recovery_end_command = '%s'",
- recoveryEndCommand)));
- }
- else if (strcmp(item->name, "archive_cleanup_command") == 0)
- {
- archiveCleanupCommand = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("archive_cleanup_command = '%s'",
- archiveCleanupCommand)));
- }
- else if (strcmp(item->name, "recovery_target_action") == 0)
- {
- if (strcmp(item->value, "pause") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
- else if (strcmp(item->value, "promote") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_PROMOTE;
- else if (strcmp(item->value, "shutdown") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
- "recovery_target_action",
- item->value),
- errhint("Valid values are \"pause\", \"promote\", and \"shutdown\".")));
+static void
+logRecoveryParameters(void)
+{
+ int normal_log_level = LOG; //DEBUG2;
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_action = '%s'",
- item->value)));
+ /*
+ * Log messages for recovery parameters at server start
+ */
+ ereport(normal_log_level,
+ (errmsg_internal("standby_mode = '%s'", (StandbyModeRequested ? "on " : "off"))));
- recoveryTargetActionSet = true;
- }
- else if (strcmp(item->name, "recovery_target_timeline") == 0)
- {
- rtliGiven = true;
- if (strcmp(item->value, "latest") == 0)
- rtli = 0;
- else
- {
- errno = 0;
- rtli = (TimeLineID) strtoul(item->value, NULL, 0);
- if (errno == EINVAL || errno == ERANGE)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_timeline is not a valid number: \"%s\"",
- item->value)));
- }
- if (rtli)
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_timeline = %u", rtli)));
- else
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_timeline = latest")));
- }
- else if (strcmp(item->name, "recovery_target_xid") == 0)
- {
- errno = 0;
- recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
- if (errno == EINVAL || errno == ERANGE)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_xid is not a valid number: \"%s\"",
- item->value)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_xid = %u",
- recoveryTargetXid)));
- recoveryTarget = RECOVERY_TARGET_XID;
- }
- else if (strcmp(item->name, "recovery_target_time") == 0)
- {
- recoveryTarget = RECOVERY_TARGET_TIME;
+ if (recoveryRestoreCommand != NULL)
+ ereport(normal_log_level,
+ (errmsg_internal("restore_command = '%s'", recoveryRestoreCommand)));
- /*
- * Convert the time string given by the user to TimestampTz form.
- */
- recoveryTargetTime =
- DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
- CStringGetDatum(item->value),
- ObjectIdGetDatum(InvalidOid),
- Int32GetDatum(-1)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_time = '%s'",
- timestamptz_to_str(recoveryTargetTime))));
- }
- else if (strcmp(item->name, "recovery_target_name") == 0)
- {
- recoveryTarget = RECOVERY_TARGET_NAME;
+ if (recoveryEndCommand != NULL)
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_end_command = '%s'", recoveryRestoreCommand)));
- recoveryTargetName = pstrdup(item->value);
- if (strlen(recoveryTargetName) >= MAXFNAMELEN)
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery_target_name is too long (maximum %d characters)",
- MAXFNAMELEN - 1)));
+ if (archiveCleanupCommand != NULL)
+ ereport(normal_log_level,
+ (errmsg_internal("archive_cleanup_command = '%s'", archiveCleanupCommand)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_name = '%s'",
- recoveryTargetName)));
- }
- else if (strcmp(item->name, "recovery_target_lsn") == 0)
- {
- recoveryTarget = RECOVERY_TARGET_LSN;
+ if (SenderConnInfo != NULL)
+ ereport(normal_log_level,
+ (errmsg_internal("sender_conninfo = '%s'", SenderConnInfo)));
- /*
- * Convert the LSN string given by the user to XLogRecPtr form.
- */
- recoveryTargetLSN =
- DatumGetLSN(DirectFunctionCall3(pg_lsn_in,
- CStringGetDatum(item->value),
- ObjectIdGetDatum(InvalidOid),
- Int32GetDatum(-1)));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_lsn = '%X/%X'",
- (uint32) (recoveryTargetLSN >> 32),
- (uint32) recoveryTargetLSN)));
- }
- else if (strcmp(item->name, "recovery_target") == 0)
- {
- if (strcmp(item->value, "immediate") == 0)
- recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
- else
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
- "recovery_target",
- item->value),
- errhint("The only allowed value is \"immediate\".")));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target = '%s'",
- item->value)));
- }
- else if (strcmp(item->name, "recovery_target_inclusive") == 0)
- {
- /*
- * does nothing if a recovery_target is not also set
- */
- if (!parse_bool(item->value, &recoveryTargetInclusive))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a Boolean value",
- "recovery_target_inclusive")));
- ereport(DEBUG2,
- (errmsg_internal("recovery_target_inclusive = %s",
- item->value)));
- }
- else if (strcmp(item->name, "standby_mode") == 0)
- {
- if (!parse_bool(item->value, &StandbyModeRequested))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a Boolean value",
- "standby_mode")));
- ereport(DEBUG2,
- (errmsg_internal("standby_mode = '%s'", item->value)));
- }
- else if (strcmp(item->name, "primary_conninfo") == 0)
- {
- PrimaryConnInfo = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("primary_conninfo = '%s'",
- PrimaryConnInfo)));
- }
- else if (strcmp(item->name, "primary_slot_name") == 0)
- {
- ReplicationSlotValidateName(item->value, ERROR);
- PrimarySlotName = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("primary_slot_name = '%s'",
- PrimarySlotName)));
- }
- else if (strcmp(item->name, "trigger_file") == 0)
+ if (SenderSlotName != NULL)
+ ereport(normal_log_level,
+ (errmsg_internal("sender_slot_name = '%s'", SenderSlotName)));
+
+ if (recovery_min_apply_delay > 0)
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_min_apply_delay = '%u'", recovery_min_apply_delay)));
+
+ /*
+ * Check details for recovery target, if any
+ */
+ if (recoveryTarget > RECOVERY_TARGET_UNSET)
+ {
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_type = '%s'", RecoveryTargetText(recoveryTarget))));
+ if (recoveryTargetValue != NULL)
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_value = '%s'", recoveryTargetValue)));
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_inclusive = '%s'", (recoveryTargetInclusive ? "on " : "off"))));
+
+ if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE)
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_timeline = '%u' (from controlfile)",
+ recoveryTargetTLI)));
+ else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_timeline = 'latest'")));
+ else
{
- TriggerFile = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("trigger_file = '%s'",
- TriggerFile)));
+ Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC);
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_timeline = '%u'",
+ recoveryTargetTLIRequested)));
}
- else if (strcmp(item->name, "recovery_min_apply_delay") == 0)
- {
- const char *hintmsg;
+ }
- if (!parse_int(item->value, &recovery_min_apply_delay, GUC_UNIT_MS,
- &hintmsg))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("parameter \"%s\" requires a temporal value",
- "recovery_min_apply_delay"),
- hintmsg ? errhint("%s", _(hintmsg)) : 0));
- ereport(DEBUG2,
- (errmsg_internal("recovery_min_apply_delay = '%s'", item->value)));
- }
- else
+ ereport(normal_log_level,
+ (errmsg_internal("recovery_target_action = '%s'", RecoveryTargetActionText(recoveryTargetAction))));
+}
+
+void
+validateRecoveryParameters(void)
+{
+ if (!ArchiveRecoveryRequested)
+ return;
+
+ if (recoveryTarget > RECOVERY_TARGET_UNSET &&
+ recoveryTargetValue == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized recovery parameter \"%s\"",
- item->name)));
- }
+ errmsg("must specify recovery_target_value when recovery_target_type is set")));
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
- if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL)
+ if (SenderConnInfo == NULL && recoveryRestoreCommand == NULL)
ereport(WARNING,
- (errmsg("recovery command file \"%s\" specified neither primary_conninfo nor restore_command",
- RECOVERY_COMMAND_FILE),
+ (errmsg("specified neither primary_conninfo nor restore_command"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
@@ -5225,8 +5150,7 @@ readRecoveryCommandFile(void)
if (recoveryRestoreCommand == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery command file \"%s\" must specify restore_command when standby mode is not enabled",
- RECOVERY_COMMAND_FILE)));
+ errmsg("must specify restore_command when standby mode is not enabled")));
}
/*
@@ -5235,50 +5159,46 @@ readRecoveryCommandFile(void)
* hot_standby = off, which was surprising behaviour.
*/
if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE &&
- recoveryTargetActionSet &&
!EnableHotStandby)
recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
/*
- * We don't support standby_mode in standalone backends; that requires
- * other processes such as the WAL receiver to be alive.
- */
- if (StandbyModeRequested && !IsUnderPostmaster)
- ereport(FATAL,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("standby mode is not supported by single-user servers")));
-
- /* Enable fetching from archive recovery area */
- ArchiveRecoveryRequested = true;
-
- /*
* If user specified recovery_target_timeline, validate it or compute the
* "latest" value. We can't do this until after we've gotten the restore
* command and set InArchiveRecovery, because we need to fetch timeline
* history files from the archive.
*/
- if (rtliGiven)
+ if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC)
{
- if (rtli)
- {
- /* Timeline 1 does not have a history file, all else should */
- if (rtli != 1 && !existsTimeLineHistory(rtli))
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery target timeline %u does not exist",
- rtli)));
- recoveryTargetTLI = rtli;
- recoveryTargetIsLatest = false;
- }
- else
- {
- /* We start the "latest" search from pg_control's timeline */
- recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
- recoveryTargetIsLatest = true;
- }
- }
+ TimeLineID rtli = recoveryTargetTLIRequested;
- FreeConfigVariables(head);
+ /* Timeline 1 does not have a history file, all else should */
+ if (rtli != 1 && !existsTimeLineHistory(rtli))
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("recovery target timeline %u does not exist",
+ rtli)));
+ recoveryTargetTLI = rtli;
+
+ /*
+ * The user has requested a specific tli. This might be the latest
+ * timeline but we don't know that; the point here is that we do not
+ * allow the recoveryTargetTLI to follow any changes.
+ */
+ recoveryTargetIsLatest = false;
+ }
+ else if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_LATEST)
+ {
+ /* We start the "latest" search from pg_control's timeline */
+ recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
+ recoveryTargetIsLatest = true;
+ }
+ else
+ {
+ /* else we just use the recoveryTargetTLI as already read from ControlFile */
+ Assert(recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_CONTROLFILE);
+ recoveryTargetIsLatest = false;
+ }
}
/*
@@ -5379,11 +5299,19 @@ exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog)
unlink(recoveryPath); /* ignore any error */
/*
- * Rename the config file out of the way, so that we don't accidentally
+ * Rename the signal files out of the way, so that we don't accidentally
* re-enter archive recovery mode in a subsequent crash.
*/
- unlink(RECOVERY_COMMAND_DONE);
- durable_rename(RECOVERY_COMMAND_FILE, RECOVERY_COMMAND_DONE, FATAL);
+ if (unlink(StandbySignalFile) != 0 && standby_signal_file_found)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not remove file \"%s\": %m",
+ StandbySignalFile)));
+ if (unlink(RecoverySignalFile) != 0 && recovery_signal_file_found)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not remove file \"%s\": %m",
+ RecoverySignalFile)));
ereport(LOG,
(errmsg("archive recovery complete")));
@@ -6114,10 +6042,9 @@ StartupXLOG(void)
recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
/*
- * Check for recovery control file, and if so set up state for offline
- * recovery
+ * Check for signal files, and if so set up state for offline recovery
*/
- readRecoveryCommandFile();
+ readRecoverySignalFile();
/*
* Save archive_cleanup_command in shared memory so that other processes
@@ -11383,7 +11310,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
* that when we later jump backwards to start redo at
* RedoStartLSN, we will have the logs streamed already.
*/
- if (PrimaryConnInfo)
+ if (SenderConnInfo)
{
XLogRecPtr ptr;
TimeLineID tli;
@@ -11404,8 +11331,8 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
tli, curFileTLI);
}
curFileTLI = tli;
- RequestXLogStreaming(tli, ptr, PrimaryConnInfo,
- PrimarySlotName);
+ RequestXLogStreaming(tli, ptr, SenderConnInfo,
+ SenderSlotName);
receivedUpto = 0;
}
@@ -11733,14 +11660,15 @@ CheckForStandbyTrigger(void)
return true;
}
- if (TriggerFile == NULL)
- return false;
-
- if (stat(TriggerFile, &stat_buf) == 0)
+ /*
+ * Check for PromoteSignalFile... this is now a constant filename, with only the
+ * filepath varying, so a user can still specify what they need.
+ */
+ if (stat(PromoteSignalFile, &stat_buf) == 0)
{
ereport(LOG,
- (errmsg("trigger file found: %s", TriggerFile)));
- unlink(TriggerFile);
+ (errmsg("promote signal file found: %s", PromoteSignalFile)));
+ unlink(PromoteSignalFile);
triggered = true;
fast_promote = true;
return true;
@@ -11748,8 +11676,8 @@ CheckForStandbyTrigger(void)
else if (errno != ENOENT)
ereport(ERROR,
(errcode_for_file_access(),
- errmsg("could not stat trigger file \"%s\": %m",
- TriggerFile)));
+ errmsg("could not stat promote signal file \"%s\": %m",
+ PromoteSignalFile)));
return false;
}
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index bc7253f..f23e131 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -328,10 +328,11 @@ pg_create_restore_point(PG_FUNCTION_ARGS)
restore_name_str = text_to_cstring(restore_name);
- if (strlen(restore_name_str) >= MAXFNAMELEN)
+ if (strlen(restore_name_str) >= MAXRESTOREPOINTNAMELEN)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("value too long for restore point (maximum %d characters)", MAXFNAMELEN - 1)));
+ errmsg("value too long for restore point (maximum %d characters)",
+ MAXRESTOREPOINTNAMELEN - 1)));
restorepoint = XLogRestorePoint(restore_name_str);
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index a7ae7e3..74b72c9 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -153,6 +153,7 @@ HandleStartupProcInterrupts(void)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
+ validateRecoveryParameters();
}
/*
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index cc3cf7d..01f62b5 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -70,9 +70,11 @@
/* GUC variables */
-int wal_receiver_status_interval;
-int wal_receiver_timeout;
-bool hot_standby_feedback;
+int wal_receiver_status_interval;
+int wal_receiver_timeout;
+bool hot_standby_feedback;
+char *SenderConnInfo;
+char *SenderSlotName;
/* libpqwalreceiver connection */
static WalReceiverConn *wrconn = NULL;
@@ -415,9 +417,33 @@ WalReceiverMain(void)
if (got_SIGHUP)
{
+ char *conninfo = pstrdup(SenderConnInfo);
+ char *slotname = pstrdup(SenderSlotName);
+
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
XLogWalRcvSendHSFeedback(true);
+
+ /*
+ * If sender_conninfo has been changed while walreceiver is running,
+ * shut down walreceiver so that a new walreceiver is started and
+ * initiates replication with the new connection information.
+ */
+ if (strcmp(conninfo, SenderConnInfo) != 0)
+ ereport(FATAL,
+ (errcode(ERRCODE_ADMIN_SHUTDOWN),
+ errmsg("closing replication connection because sender_conninfo was changed")));
+
+ /*
+ * And the same for sender_slot_name.
+ */
+ if (strcmp(slotname, SenderSlotName) != 0)
+ ereport(FATAL,
+ (errcode(ERRCODE_ADMIN_SHUTDOWN),
+ errmsg("closing replication connection because sender_slot_name was changed")));
+
+ pfree(conninfo);
+ pfree(slotname);
}
/* See if we can read data immediately */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 946ba9e..b560e48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -31,6 +31,7 @@
#include "access/transam.h"
#include "access/twophase.h"
#include "access/xact.h"
+#include "access/xlog.h"
#include "catalog/namespace.h"
#include "commands/async.h"
#include "commands/prepare.h"
@@ -77,6 +78,7 @@
#include "utils/guc_tables.h"
#include "utils/memutils.h"
#include "utils/pg_locale.h"
+#include "utils/pg_lsn.h"
#include "utils/plancache.h"
#include "utils/portal.h"
#include "utils/ps_status.h"
@@ -182,6 +184,15 @@ static void assign_application_name(const char *newval, void *extra);
static bool check_cluster_name(char **newval, void **extra, GucSource source);
static const char *show_unix_socket_permissions(void);
static const char *show_log_file_mode(void);
+static bool check_recovery_target_type(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_type(const char *newval, void *extra);
+static bool check_recovery_target_value(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_value(const char *newval, void *extra);
+static bool check_recovery_target_timeline(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_timeline(const char *newval, void *extra);
+static bool check_recovery_target_action(char **newval, void **extra, GucSource source);
+static void assign_recovery_target_action(const char *newval, void *extra);
+static bool check_sender_slot_name(char **newval, void **extra, GucSource source);
/* Private functions in guc-file.l that need to be called from guc.c */
static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -459,6 +470,9 @@ char *IdentFileName;
char *external_pid_file;
char *pgstat_temp_directory;
+char *signal_file_directory;
+char *recovery_target_timeline_string;
+char *recovery_target_action_string;
char *application_name;
@@ -592,6 +606,10 @@ const char *const config_group_names[] =
gettext_noop("Write-Ahead Log / Checkpoints"),
/* WAL_ARCHIVING */
gettext_noop("Write-Ahead Log / Archiving"),
+ /* WAL_ARCHIVE_RECOVERY */
+ gettext_noop("Write-Ahead Log / Archive Recovery"),
+ /* WAL_RECOVERY_TARGET */
+ gettext_noop("Write-Ahead Log / Recovery Target"),
/* REPLICATION */
gettext_noop("Replication"),
/* REPLICATION_SENDING */
@@ -1549,6 +1567,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"recovery_target_inclusive", PGC_SIGHUP, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets whether to include or exclude transaction with recovery target."),
+ NULL
+ },
+ &recoveryTargetInclusive,
+ true,
+ NULL, NULL, NULL
+ },
+
+ {
{"hot_standby", PGC_POSTMASTER, REPLICATION_STANDBY,
gettext_noop("Allows connections and queries during recovery."),
NULL
@@ -1789,8 +1817,19 @@ static struct config_int ConfigureNamesInt[] =
},
{
+ {"recovery_min_apply_delay", PGC_SIGHUP, REPLICATION_STANDBY,
+ gettext_noop("Sets the minimum delay to apply changes during recovery."),
+ NULL,
+ GUC_UNIT_MS
+ },
+ &recovery_min_apply_delay,
+ 0, 0, INT_MAX,
+ NULL, NULL, NULL
+ },
+
+ {
{"wal_receiver_status_interval", PGC_SIGHUP, REPLICATION_STANDBY,
- gettext_noop("Sets the maximum interval between WAL receiver status reports to the primary."),
+ gettext_noop("Sets the maximum interval between WAL receiver status reports to the sending server."),
NULL,
GUC_UNIT_S
},
@@ -1801,7 +1840,7 @@ static struct config_int ConfigureNamesInt[] =
{
{"wal_receiver_timeout", PGC_SIGHUP, REPLICATION_STANDBY,
- gettext_noop("Sets the maximum wait time to receive data from the primary."),
+ gettext_noop("Sets the maximum wait time to receive data from the sending server."),
NULL,
GUC_UNIT_MS
},
@@ -2996,6 +3035,98 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"restore_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the shell command that will retrieve an archived WAL file."),
+ NULL
+ },
+ &recoveryRestoreCommand,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the shell command that will be executed at every restartpoint."),
+ NULL
+ },
+ &archiveCleanupCommand,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"recovery_end_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the shell command that will be executed once only at the end of recovery."),
+ NULL
+ },
+ &recoveryEndCommand,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"recovery_target_type", PGC_SIGHUP, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the type of desired recovery target."),
+ NULL
+ },
+ &recoveryTargetTypeString,
+ "",
+ check_recovery_target_type, assign_recovery_target_type, NULL
+ },
+
+ {
+ {"recovery_target_value", PGC_SIGHUP, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the value of the recovery taregt up to which recovery will proceed."),
+ NULL
+ },
+ &recoveryTargetValue,
+ "",
+ check_recovery_target_value, assign_recovery_target_value, NULL
+ },
+
+ {
+ {"recovery_target_timeline", PGC_SIGHUP, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets recovering into a particular timeline."),
+ NULL
+ },
+ &recovery_target_timeline_string,
+ "",
+ check_recovery_target_timeline, assign_recovery_target_timeline, NULL
+ },
+
+ {
+ {"recovery_target_action", PGC_SIGHUP, WAL_RECOVERY_TARGET,
+ gettext_noop("Sets the action to perform upon reaching the recovery target."),
+ NULL
+ },
+ &recovery_target_action_string,
+ "",
+ check_recovery_target_action, assign_recovery_target_action, NULL
+ },
+
+ {
+ {"sender_conninfo", PGC_SIGHUP, REPLICATION_STANDBY,
+ gettext_noop("Sets the connection string to be used to connect with the sending server."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &SenderConnInfo,
+ "",
+ NULL, NULL, NULL
+ },
+
+ {
+ {"sender_slot_name", PGC_SIGHUP, REPLICATION_STANDBY,
+ gettext_noop("Sets the name of the replication slot to use on the sending server."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &SenderSlotName,
+ "",
+ check_sender_slot_name, NULL, NULL
+ },
+
+ {
{"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE,
gettext_noop("Sets the client's character set encoding."),
NULL,
@@ -3475,6 +3606,17 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"signal_file_directory", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Signal files may be written to the specified directory."),
+ NULL,
+ GUC_SUPERUSER_ONLY
+ },
+ &signal_file_directory,
+ NULL,
+ check_canonical_path, NULL, NULL
+ },
+
+ {
{"stats_temp_directory", PGC_SIGHUP, STATS_COLLECTOR,
gettext_noop("Writes temporary statistics files to the specified directory."),
NULL,
@@ -10335,4 +10477,301 @@ show_log_file_mode(void)
return buf;
}
+static bool
+check_recovery_target_type(char **newval, void **extra, GucSource source)
+{
+ RecoveryTargetType *myextra;
+ RecoveryTargetType rt = RECOVERY_TARGET_UNSET;
+
+ if (strcmp(*newval, "xid") == 0)
+ rt = RECOVERY_TARGET_XID;
+ else if (strcmp(*newval, "timestamp") == 0)
+ rt = RECOVERY_TARGET_TIME;
+ else if (strcmp(*newval, "name") == 0)
+ rt = RECOVERY_TARGET_NAME;
+ else if (strcmp(*newval, "lsn") == 0)
+ rt = RECOVERY_TARGET_LSN;
+ else if (strcmp(*newval, "immediate") == 0)
+ rt = RECOVERY_TARGET_IMMEDIATE;
+ else if (strcmp(*newval, "") != 0)
+ {
+ GUC_check_errdetail("recovery_target_type is not valid: \"%s\"", *newval);
+ return false;
+ }
+
+ myextra = (RecoveryTargetType *) guc_malloc(ERROR, sizeof(RecoveryTargetType));
+ *myextra = rt;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+assign_recovery_target_type(const char *newval, void *extra)
+{
+ recoveryTarget = *((RecoveryTargetType *) extra);
+}
+
+static bool
+check_recovery_target_value(char **newval, void **extra, GucSource source)
+{
+ bool valid = false;
+
+ /*
+ * Value must be present in some cases, must not be present in others
+ */
+ if (strcmp(*newval, "") == 0)
+ {
+ if (recoveryTarget == RECOVERY_TARGET_UNSET ||
+ recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
+ valid = true;
+ }
+ else
+ {
+ if (recoveryTarget == RECOVERY_TARGET_UNSET ||
+ recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
+ valid = false;
+ else
+ valid = true;
+ }
+
+ if (!valid)
+ {
+ GUC_check_errdetail("recovery_target_value is not valid: \"%s\"", *newval);
+ return false;
+ }
+
+ /*
+ * We assume that recovery_target_type has already been parsed
+ * since it sorts alphabetically before recovery_target_value.
+ */
+ switch (recoveryTarget)
+ {
+ case RECOVERY_TARGET_UNSET:
+ case RECOVERY_TARGET_IMMEDIATE:
+ /* No value, so do nothing */
+ break;
+
+ case RECOVERY_TARGET_XID:
+ {
+ TransactionId xid;
+ TransactionId *myextra;
+
+ errno = 0;
+ xid = (TransactionId) strtoul(*newval, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ {
+ GUC_check_errdetail("recovery_target_value is not a valid number: \"%s\"",
+ *newval);
+ return false;
+ }
+
+ myextra = (TransactionId *) guc_malloc(ERROR, sizeof(TransactionId));
+ *myextra = xid;
+ *extra = (void *) myextra;
+ }
+ break;
+
+ case RECOVERY_TARGET_TIME:
+ {
+ TimestampTz time;
+ TimestampTz *myextra;
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ PG_TRY();
+ {
+ time = DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
+ CStringGetDatum(*newval),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error message */
+ GUC_check_errdetail("%s", edata->message);
+ FreeErrorData(edata);
+ return false;
+ }
+ PG_END_TRY();
+
+ myextra = (TimestampTz *) guc_malloc(ERROR, sizeof(TimestampTz));
+ *myextra = time;
+ *extra = (void *) myextra;
+ }
+ break;
+
+ case RECOVERY_TARGET_NAME:
+ /* Use the value of newval directly */
+ if (strlen(*newval) > MAXRESTOREPOINTNAMELEN)
+ {
+ GUC_check_errdetail("recovery_target_value is too long (maximum %d characters)",
+ MAXRESTOREPOINTNAMELEN);
+ return false;
+ }
+ break;
+
+ case RECOVERY_TARGET_LSN:
+ {
+ XLogRecPtr lsn;
+ XLogRecPtr *myextra;
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ /*
+ * Convert the LSN string given by the user to XLogRecPtr form.
+ */
+ PG_TRY();
+ {
+ lsn =
+ DatumGetLSN(DirectFunctionCall3(pg_lsn_in,
+ CStringGetDatum(*newval),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1)));
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error message */
+ GUC_check_errdetail("%s", edata->message);
+ FreeErrorData(edata);
+ return false;
+ }
+ PG_END_TRY();
+
+ myextra = (XLogRecPtr *) guc_malloc(ERROR, sizeof(XLogRecPtr));
+ *myextra = lsn;
+ *extra = (void *) myextra;
+ }
+ break;
+ }
+
+ return true;
+}
+
+static void
+assign_recovery_target_value(const char *newval, void *extra)
+{
+ switch (recoveryTarget)
+ {
+ case RECOVERY_TARGET_UNSET:
+ case RECOVERY_TARGET_IMMEDIATE:
+ break;
+
+ case RECOVERY_TARGET_XID:
+ recoveryTargetXid = *((TransactionId *) extra);
+ break;
+
+ case RECOVERY_TARGET_TIME:
+ recoveryTargetTime = *((TimestampTz *) extra);
+ break;
+
+ case RECOVERY_TARGET_NAME:
+ if (newval && *newval)
+ recoveryTargetName = (char *) newval;
+ break;
+
+ case RECOVERY_TARGET_LSN:
+ recoveryTargetLSN = *((XLogRecPtr *) extra);
+ break;
+ }
+}
+
+static bool
+check_recovery_target_timeline(char **newval, void **extra, GucSource source)
+{
+ RecoveryTargetTimeLineGoal rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
+ RecoveryTargetTimeLineGoal *myextra;
+
+ if (strcmp(*newval, "latest") == 0)
+ rttg = RECOVERY_TARGET_TIMELINE_LATEST;
+ else if (strcmp(*newval, "controlfile") == 0 || strcmp(*newval, "") == 0)
+ rttg = RECOVERY_TARGET_TIMELINE_CONTROLFILE;
+ else
+ {
+ TimeLineID tli;
+
+ errno = 0;
+ tli = (TimeLineID) strtoul(*newval, NULL, 0);
+ if (errno == EINVAL || errno == ERANGE)
+ {
+ GUC_check_errdetail("recovery_target_timeline is not a valid number: \"%s\"",
+ *newval);
+ return false;
+ }
+ rttg = RECOVERY_TARGET_TIMELINE_NUMERIC;
+ }
+
+ myextra = (TimeLineID *) guc_malloc(ERROR, sizeof(TimeLineID));
+ *myextra = rttg;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+assign_recovery_target_timeline(const char *newval, void *extra)
+{
+ recoveryTargetTimeLineGoal = *((TimeLineID *) extra);
+ if (recoveryTargetTimeLineGoal == RECOVERY_TARGET_TIMELINE_NUMERIC)
+ recoveryTargetTLIRequested = (TimeLineID) strtoul(newval, NULL, 0);
+ else
+ recoveryTargetTLIRequested = 0;
+}
+
+static bool
+check_recovery_target_action(char **newval, void **extra, GucSource source)
+{
+ RecoveryTargetAction rta = RECOVERY_TARGET_ACTION_PAUSE;
+ RecoveryTargetAction *myextra;
+
+ if (strcmp(*newval, "pause") == 0)
+ rta = RECOVERY_TARGET_ACTION_PAUSE;
+ else if (strcmp(*newval, "promote") == 0)
+ rta = RECOVERY_TARGET_ACTION_PROMOTE;
+ else if (strcmp(*newval, "shutdown") == 0)
+ rta = RECOVERY_TARGET_ACTION_SHUTDOWN;
+ else if (strcmp(*newval, "") != 0)
+ {
+ GUC_check_errdetail("recovery_target_action is not valid: \"%s\"", *newval);
+ return false;
+ }
+
+ myextra = (RecoveryTargetAction *) guc_malloc(ERROR, sizeof(RecoveryTargetAction));
+ *myextra = rta;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+assign_recovery_target_action(const char *newval, void *extra)
+{
+ recoveryTargetAction = *((RecoveryTargetAction *) extra);
+}
+
+static bool
+check_sender_slot_name(char **newval, void **extra, GucSource source)
+{
+ if (strcmp(*newval,"") != 0 &&
+ !ReplicationSlotValidateName(*newval, WARNING))
+ {
+ GUC_check_errdetail("sender_slot_name is not valid: \"%s\"", *newval);
+ return false;
+ }
+
+ return true;
+}
+
#include "guc-file.c"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee8232f..2ee3bca 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -45,6 +45,9 @@
#ident_file = 'ConfigDir/pg_ident.conf' # ident configuration file
# (change requires restart)
+#signal_file_directory = 'ConfigDir' # signal files for recovery/replication
+ # (change requires restart)
+
# If external_pid_file is not explicitly set, no extra PID file is written.
#external_pid_file = '' # write an extra PID file
# (change requires restart)
@@ -221,6 +224,26 @@
#archive_timeout = 0 # force a logfile segment switch after this
# number of seconds; 0 disables
+# - Archive Recovery -
+#restore_command = '' # command to use to restore an archived logfile segment
+ # placeholders: %p = path of file to restore
+ # %f = file name only
+ # e.g. 'cp /mnt/server/archivedir/%f %p'
+#archive_cleanup_command = '' # command to execute at every restartpoint
+#recovery_end_command = '' # command to execute at completion of recovery
+
+# - Recovery Target -
+
+# Set these only when performing a targeted recovery
+
+#recovery_target_type='' # 'xid', 'time', 'name', 'lsn',
+ # or 'immediate'
+#recovery_target_value='' # value interpreted according to type
+#recovery_target_inclusive = on
+#recovery_target_timeline = '' # unset means read from controlfile (default),
+ # or set to 'latest' or timeline ID
+#recovery_target_action = '' # 'pause', 'promote', 'shutdown'
+
#------------------------------------------------------------------------------
# REPLICATION
@@ -254,6 +277,8 @@
# These settings are ignored on a master server.
+#sender_conninfo = '' # connection string on sending server
+#sender_slot_name = '' # connection slot on sending server
#hot_standby = off # "on" allows queries during recovery
# (change requires restart)
#max_standby_archive_delay = 30s # max delay before canceling queries
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 7d21408..8b6dfca 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -87,6 +87,41 @@ typedef enum
RECOVERY_TARGET_IMMEDIATE
} RecoveryTargetType;
+#define RecoveryTargetText(t) ( \
+ t == RECOVERY_TARGET_UNSET ? "unset" : ( \
+ t == RECOVERY_TARGET_XID ? "xid" : ( \
+ t == RECOVERY_TARGET_TIME ? "timestamp" : ( \
+ t == RECOVERY_TARGET_NAME ? "name" : ( \
+ t == RECOVERY_TARGET_LSN ? "lsn" : \
+ "immediate" )))))
+
+/*
+ * Recovery target action.
+ */
+typedef enum
+{
+ RECOVERY_TARGET_ACTION_PAUSE,
+ RECOVERY_TARGET_ACTION_PROMOTE,
+ RECOVERY_TARGET_ACTION_SHUTDOWN
+} RecoveryTargetAction;
+
+#define RecoveryTargetActionText(t) ( \
+ t == RECOVERY_TARGET_ACTION_PAUSE ? "pause" : ( \
+ t == RECOVERY_TARGET_ACTION_PROMOTE ? "promote" : ( \
+ "shutdown" )))
+/*
+ * Recovery target TimeLine goal
+ */
+typedef enum
+{
+ RECOVERY_TARGET_TIMELINE_CONTROLFILE,
+ RECOVERY_TARGET_TIMELINE_LATEST,
+ RECOVERY_TARGET_TIMELINE_NUMERIC
+} RecoveryTargetTimeLineGoal;
+
+/* Max length of named restore points */
+#define MAXRESTOREPOINTNAMELEN 64
+
extern XLogRecPtr ProcLastRecPtr;
extern XLogRecPtr XactLastRecEnd;
extern PGDLLIMPORT XLogRecPtr XactLastCommitEnd;
@@ -109,6 +144,36 @@ extern bool log_checkpoints;
extern int CheckPointSegments;
+/* options previously taken from recovery.conf for archive recovery */
+extern char *recoveryRestoreCommand;
+extern char *recoveryEndCommand;
+extern char *archiveCleanupCommand;
+extern char *recoveryTargetTypeString;
+extern RecoveryTargetType recoveryTarget;
+extern char *recoveryTargetValue;
+extern bool recoveryTargetInclusive;
+extern RecoveryTargetAction recoveryTargetAction;
+extern TransactionId recoveryTargetXid;
+extern TimestampTz recoveryTargetTime;
+extern char *recoveryTargetName;
+extern XLogRecPtr recoveryTargetLSN;
+extern int recovery_min_apply_delay;
+
+/* option set locally in Startup process only when signal files exist */
+extern bool StandbyModeRequested;
+extern bool StandbyMode;
+
+/* options for WALreceiver.c */
+extern char *SenderConnInfo;
+extern char *SenderSlotName;
+
+extern char *signal_file_directory;
+
+extern char *recoveryTargetTLIString;
+extern RecoveryTargetTimeLineGoal recoveryTargetTimeLineGoal;
+extern TimeLineID recoveryTargetTLIRequested;
+extern TimeLineID recoveryTargetTLI;
+
/* Archive modes */
typedef enum ArchiveMode
{
@@ -238,6 +303,7 @@ extern const char *xlog_identify(uint8 info);
extern void issue_xlog_fsync(int fd, XLogSegNo segno);
+extern void validateRecoveryParameters(void);
extern bool RecoveryInProgress(void);
extern bool HotStandbyActive(void);
extern bool HotStandbyActiveInReplay(void);
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 05f996b..1a80377 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -246,16 +246,6 @@ typedef struct XLogRecData
} XLogRecData;
/*
- * Recovery target action.
- */
-typedef enum
-{
- RECOVERY_TARGET_ACTION_PAUSE,
- RECOVERY_TARGET_ACTION_PROMOTE,
- RECOVERY_TARGET_ACTION_SHUTDOWN
-} RecoveryTargetAction;
-
-/*
* Method table for resource managers.
*
* This struct must be kept in sync with the PG_RMGR definition in
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index b695f61..7f4389c 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -68,6 +68,8 @@ enum config_group
WAL_SETTINGS,
WAL_CHECKPOINTS,
WAL_ARCHIVING,
+ WAL_ARCHIVE_RECOVERY,
+ WAL_RECOVERY_TARGET,
REPLICATION,
REPLICATION_SENDING,
REPLICATION_MASTER,
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index c1b16ca..60ad78e 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -574,8 +574,6 @@ of a backup previously created on that node with $node->backup.
Does not start the node after initializing it.
-A recovery.conf is not created.
-
pg_hba.conf is configured to allow replication connections. Pass the keyword
parameter hba_permit_replication => 0 to disable this.
@@ -745,13 +743,16 @@ sub enable_streaming
my ($self, $root_node) = @_;
my $root_connstr = $root_node->connstr;
my $name = $self->name;
+ my $pgdata = $self->data_dir;
print "### Enabling streaming replication for node \"$name\"\n";
$self->append_conf(
- 'recovery.conf', qq(
-primary_conninfo='$root_connstr application_name=$name'
-standby_mode=on
+ 'postgresql.conf', qq(
+sender_conninfo='$root_connstr application_name=$name'
));
+ open my $standbysignal, ">>$pgdata/standby.signal";
+ print $standbysignal "\n# Allow replication (set up by PostgresNode.pm)\n";
+ close $standbysignal;
}
# Internal routine to enable archive recovery command on a standby node
@@ -760,6 +761,7 @@ sub enable_restoring
my ($self, $root_node) = @_;
my $path = $root_node->archive_dir;
my $name = $self->name;
+ my $pgdata = $self->data_dir;
print "### Enabling WAL restore for node \"$name\"\n";
@@ -776,10 +778,12 @@ sub enable_restoring
: qq{cp "$path/%f" "%p"};
$self->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
restore_command = '$copy_command'
-standby_mode = on
));
+ open my $recoverysignal, ">>$pgdata/recovery.signal";
+ print $recoverysignal "\n# Allow recovery (set up by PostgresNode.pm)\n";
+ close $recoverysignal;
}
# Internal routine to enable archiving
diff --git a/src/test/recovery/t/003_recovery_targets.pl b/src/test/recovery/t/003_recovery_targets.pl
index a82545b..236e651 100644
--- a/src/test/recovery/t/003_recovery_targets.pl
+++ b/src/test/recovery/t/003_recovery_targets.pl
@@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 9;
+use Test::More tests => 5;
# Create and test a standby from given backup, with a certain
# recovery target.
@@ -23,7 +23,7 @@ sub test_recovery_standby
foreach my $param_item (@$recovery_params)
{
$node_standby->append_conf(
- 'recovery.conf',
+ 'postgresql.conf',
qq($param_item
));
}
@@ -100,47 +100,20 @@ $node_master->safe_psql('postgres',
$node_master->safe_psql('postgres', "SELECT pg_switch_xlog()");
# Test recovery targets
-my @recovery_params = ("recovery_target = 'immediate'");
+my @recovery_params = ("recovery_target_type = 'immediate'");
test_recovery_standby('immediate target',
'standby_1', $node_master, \@recovery_params, "1000", $lsn1);
-@recovery_params = ("recovery_target_xid = '$recovery_txid'");
+@recovery_params = ("recovery_target_type = 'xid'", "recovery_target_value = '$recovery_txid'");
test_recovery_standby('XID', 'standby_2', $node_master, \@recovery_params,
"2000", $lsn2);
-@recovery_params = ("recovery_target_time = '$recovery_time'");
+@recovery_params = ("recovery_target_type = 'timestamp'", "recovery_target_value = '$recovery_time'");
test_recovery_standby('time', 'standby_3', $node_master, \@recovery_params,
"3000", $lsn3);
-@recovery_params = ("recovery_target_name = '$recovery_name'");
+@recovery_params = ("recovery_target_type = 'name'", "recovery_target_value = '$recovery_name'");
test_recovery_standby('name', 'standby_4', $node_master, \@recovery_params,
"4000", $lsn4);
-@recovery_params = ("recovery_target_lsn = '$recovery_lsn'");
+@recovery_params = ("recovery_target_type = 'lsn'", "recovery_target_value = '$recovery_lsn'");
test_recovery_standby('LSN', 'standby_5', $node_master, \@recovery_params,
"5000", $lsn5);
-# Multiple targets
-# Last entry has priority (note that an array respects the order of items
-# not hashes).
-@recovery_params = (
- "recovery_target_name = '$recovery_name'",
- "recovery_target_xid = '$recovery_txid'",
- "recovery_target_time = '$recovery_time'");
-test_recovery_standby('name + XID + time',
- 'standby_6', $node_master, \@recovery_params, "3000", $lsn3);
-@recovery_params = (
- "recovery_target_time = '$recovery_time'",
- "recovery_target_name = '$recovery_name'",
- "recovery_target_xid = '$recovery_txid'");
-test_recovery_standby('time + name + XID',
- 'standby_7', $node_master, \@recovery_params, "2000", $lsn2);
-@recovery_params = (
- "recovery_target_xid = '$recovery_txid'",
- "recovery_target_time = '$recovery_time'",
- "recovery_target_name = '$recovery_name'");
-test_recovery_standby('XID + time + name',
- 'standby_8', $node_master, \@recovery_params, "4000", $lsn4);
-@recovery_params = (
- "recovery_target_xid = '$recovery_txid'",
- "recovery_target_time = '$recovery_time'",
- "recovery_target_name = '$recovery_name'",
- "recovery_target_lsn = '$recovery_lsn'",);
-test_recovery_standby('XID + time + name + LSN',
- 'standby_9', $node_master, \@recovery_params, "5000", $lsn5);
+# Tests for multiple targets no longer needed from 10.0 onwards
diff --git a/src/test/recovery/t/004_timeline_switch.pl b/src/test/recovery/t/004_timeline_switch.pl
index 5f3b2fe..d73ffa6 100644
--- a/src/test/recovery/t/004_timeline_switch.pl
+++ b/src/test/recovery/t/004_timeline_switch.pl
@@ -46,12 +46,10 @@ $node_master->teardown_node;
$node_standby_1->promote;
# Switch standby 2 to replay from standby 1
-rmtree($node_standby_2->data_dir . '/recovery.conf');
my $connstr_1 = $node_standby_1->connstr;
$node_standby_2->append_conf(
- 'recovery.conf', qq(
-primary_conninfo='$connstr_1'
-standby_mode=on
+ 'postgresql.conf', qq(
+sender_conninfo='$connstr_1'
recovery_target_timeline='latest'
));
$node_standby_2->restart;
diff --git a/src/test/recovery/t/005_replay_delay.pl b/src/test/recovery/t/005_replay_delay.pl
index 640295b..ab87faf 100644
--- a/src/test/recovery/t/005_replay_delay.pl
+++ b/src/test/recovery/t/005_replay_delay.pl
@@ -25,7 +25,7 @@ my $delay = 3;
$node_standby->init_from_backup($node_master, $backup_name,
has_streaming => 1);
$node_standby->append_conf(
- 'recovery.conf', qq(
+ 'postgresql.conf', qq(
recovery_min_apply_delay = '${delay}s'
));
$node_standby->start;