Commit 9e403c25 authored by Heikki Linnakangas's avatar Heikki Linnakangas

Add recovery_end_command option to recovery.conf. recovery_end_command

is run at the end of archive recovery, providing a chance to do external
cleanup. Modify pg_standby so that it no longer removes the trigger file,
that is to be done using the recovery_end_command now.

Provide a "smart" failover mode in pg_standby, where we don't fail over
immediately, but only after recovering all unapplied WAL from the archive.
That gives you zero data loss assuming all WAL was archived before
failover, which is what most users of pg_standby actually want.

recovery_end_command by Simon Riggs, pg_standby changes by Fujii Masao and
myself.
parent a7107136
This diff is collapsed.
<!-- $PostgreSQL: pgsql/doc/src/sgml/backup.sgml,v 2.125 2009/04/27 16:27:35 momjian Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/backup.sgml,v 2.126 2009/05/14 20:31:09 heikki Exp $ -->
<chapter id="backup"> <chapter id="backup">
<title>Backup and Restore</title> <title>Backup and Restore</title>
...@@ -1126,6 +1126,29 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows ...@@ -1126,6 +1126,29 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry id="recovery-end-command" xreflabel="recovery_end_command">
<term><varname>recovery_end_command</varname> (<type>string</type>)</term>
<listitem>
<para>
This parameter specifies a shell command that will be executed once only
at the end of recovery. This parameter is optional. The purpose of the
recovery_end_command is to provide a mechanism for cleanup following
replication or recovery.
Any <literal>%r</> is replaced by the name of the file
containing the last valid restart point. That is the earliest file that
must be kept to allow a restore to be restartable, so this information
can be used to truncate the archive to just the minimum required to
support restart of the current restore. <literal>%r</> would only be
used in a warm-standby configuration (see <xref linkend="warm-standby">).
Write <literal>%%</> to embed an actual <literal>%</> character
in the command.
If the command returns a non-zero exit status then a WARNING log
message will be written, unless signalled in which case we return
a FATAL error.
</para>
</listitem>
</varlistentry>
<varlistentry id="recovery-target-time" xreflabel="recovery_target_time"> <varlistentry id="recovery-target-time" xreflabel="recovery_target_time">
<term><varname>recovery_target_time</varname> <term><varname>recovery_target_time</varname>
(<type>timestamp</type>) (<type>timestamp</type>)
......
<!-- $PostgreSQL: pgsql/doc/src/sgml/pgstandby.sgml,v 2.7 2009/02/27 09:30:21 petere Exp $ --> <!-- $PostgreSQL: pgsql/doc/src/sgml/pgstandby.sgml,v 2.8 2009/05/14 20:31:09 heikki Exp $ -->
<sect1 id="pgstandby"> <sect1 id="pgstandby">
<title>pg_standby</title> <title>pg_standby</title>
...@@ -92,6 +92,37 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv ...@@ -92,6 +92,37 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv
is specified, is specified,
the <replaceable>archivelocation</> directory must be writable too. the <replaceable>archivelocation</> directory must be writable too.
</para> </para>
<para>
There are two ways to fail over a <quote>warm standby</> database server.
You control the type of failover with the contents of the trigger file:
<variablelist>
<varlistentry>
<term>Smart Failover</term>
<listitem>
<para>
In smart failover, the server is brought up after applying all
WAL files available in the archive. This results in zero data loss,
even if the standby server has fallen behind, but if there is a lot
unapplied WAL the recovery can take a long time. To trigger a smart
failover, create a trigger file containing the word <literal>smart</>,
or just leave it empty.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Fast Failover</term>
<listitem>
<para>
In fast failover, the server is brought up immediately. Any WAL files
in the archive that have not yet been applied will be ignored, and
all transactions in those files are lost. To trigger a fast failover,
write the word <literal>fast</> into the trigger file.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<table> <table>
<title><application>pg_standby</> options</title> <title><application>pg_standby</> options</title>
...@@ -177,8 +208,7 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv ...@@ -177,8 +208,7 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv
<entry><literal>-t</> <replaceable>triggerfile</></entry> <entry><literal>-t</> <replaceable>triggerfile</></entry>
<entry>none</entry> <entry>none</entry>
<entry> <entry>
Specify a trigger file whose presence should cause recovery to end Specify a trigger file whose presence should perform failover.
whether or not the next WAL file is available.
It is recommended that you use a structured filename to It is recommended that you use a structured filename to
avoid confusion as to which server is being triggered avoid confusion as to which server is being triggered
when multiple servers exist on the same system; for example when multiple servers exist on the same system; for example
...@@ -190,7 +220,7 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv ...@@ -190,7 +220,7 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv
<entry>0</entry> <entry>0</entry>
<entry> <entry>
Set the maximum number of seconds to wait for the next WAL file, Set the maximum number of seconds to wait for the next WAL file,
after which recovery will end and the standby will come up. after which a fast failover will be performed.
A setting of zero (the default) means wait forever. A setting of zero (the default) means wait forever.
The default setting is not necessarily recommended; The default setting is not necessarily recommended;
consult <xref linkend="warm-standby"> for discussion. consult <xref linkend="warm-standby"> for discussion.
...@@ -210,6 +240,7 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv ...@@ -210,6 +240,7 @@ pg_standby <optional> <replaceable>option</> ... </optional> <replaceable>archiv
archive_command = 'cp %p .../archive/%f' archive_command = 'cp %p .../archive/%f'
restore_command = 'pg_standby -l -d -s 2 -t /tmp/pgsql.trigger.5442 .../archive %f %p %r 2>>standby.log' restore_command = 'pg_standby -l -d -s 2 -t /tmp/pgsql.trigger.5442 .../archive %f %p %r 2>>standby.log'
recovery_end_command = 'rm -f /tmp/pgsql.trigger.5442'
</programlisting> </programlisting>
<para> <para>
where the archive directory is physically located on the standby server, where the archive directory is physically located on the standby server,
...@@ -236,7 +267,13 @@ restore_command = 'pg_standby -l -d -s 2 -t /tmp/pgsql.trigger.5442 .../archive ...@@ -236,7 +267,13 @@ restore_command = 'pg_standby -l -d -s 2 -t /tmp/pgsql.trigger.5442 .../archive
<listitem> <listitem>
<para> <para>
stop waiting only when a trigger file called stop waiting only when a trigger file called
<filename>/tmp/pgsql.trigger.5442</> appears <filename>/tmp/pgsql.trigger.5442</> appears,
and perform failover according to its content
</para>
</listitem>
<listitem>
<para>
remove the trigger file when recovery ends
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
...@@ -277,7 +314,8 @@ restore_command = 'pg_standby -d -s 5 -t C:\pgsql.trigger.5442 ...\archive %f %p ...@@ -277,7 +314,8 @@ restore_command = 'pg_standby -d -s 5 -t C:\pgsql.trigger.5442 ...\archive %f %p
<listitem> <listitem>
<para> <para>
stop waiting only when a trigger file called stop waiting only when a trigger file called
<filename>C:\pgsql.trigger.5442</> appears <filename>C:\pgsql.trigger.5442</> appears,
and perform failover according to its content
</para> </para>
</listitem> </listitem>
<listitem> <listitem>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.337 2009/05/07 11:25:25 heikki Exp $ * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.338 2009/05/14 20:31:09 heikki Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -147,6 +147,7 @@ static bool restoredFromArchive = false; ...@@ -147,6 +147,7 @@ static bool restoredFromArchive = false;
/* options taken from recovery.conf */ /* options taken from recovery.conf */
static char *recoveryRestoreCommand = NULL; static char *recoveryRestoreCommand = NULL;
static char *recoveryEndCommand = NULL;
static bool recoveryTarget = false; static bool recoveryTarget = false;
static bool recoveryTargetExact = false; static bool recoveryTargetExact = false;
static bool recoveryTargetInclusive = true; static bool recoveryTargetInclusive = true;
...@@ -463,6 +464,7 @@ static int XLogFileRead(uint32 log, uint32 seg, int emode); ...@@ -463,6 +464,7 @@ static int XLogFileRead(uint32 log, uint32 seg, int emode);
static void XLogFileClose(void); static void XLogFileClose(void);
static bool RestoreArchivedFile(char *path, const char *xlogfname, static bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize); const char *recovername, off_t expectedSize);
static void ExecuteRecoveryEndCommand(void);
static void PreallocXlogFiles(XLogRecPtr endptr); static void PreallocXlogFiles(XLogRecPtr endptr);
static void RemoveOldXlogFiles(uint32 log, uint32 seg, XLogRecPtr endptr); static void RemoveOldXlogFiles(uint32 log, uint32 seg, XLogRecPtr endptr);
static void ValidateXLOGDirectoryStructure(void); static void ValidateXLOGDirectoryStructure(void);
...@@ -2849,6 +2851,114 @@ RestoreArchivedFile(char *path, const char *xlogfname, ...@@ -2849,6 +2851,114 @@ RestoreArchivedFile(char *path, const char *xlogfname,
return false; return false;
} }
/*
* Attempt to execute the recovery_end_command.
*/
static void
ExecuteRecoveryEndCommand(void)
{
char xlogRecoveryEndCmd[MAXPGPATH];
char lastRestartPointFname[MAXPGPATH];
char *dp;
char *endp;
const char *sp;
int rc;
bool signaled;
uint32 restartLog;
uint32 restartSeg;
Assert(recoveryEndCommand);
/*
* Calculate the archive file cutoff point for use during log shipping
* replication. All files earlier than this point can be deleted
* from the archive, though there is no requirement to do so.
*
* We initialise this with the filename of an InvalidXLogRecPtr, which
* will prevent the deletion of any WAL files from the archive
* because of the alphabetic sorting property of WAL filenames.
*
* Once we have successfully located the redo pointer of the checkpoint
* from which we start recovery we never request a file prior to the redo
* pointer of the last restartpoint. When redo begins we know that we
* have successfully located it, so there is no need for additional
* status flags to signify the point when we can begin deleting WAL files
* from the archive.
*/
if (InRedo)
{
XLByteToSeg(ControlFile->checkPointCopy.redo,
restartLog, restartSeg);
XLogFileName(lastRestartPointFname,
ControlFile->checkPointCopy.ThisTimeLineID,
restartLog, restartSeg);
}
else
XLogFileName(lastRestartPointFname, 0, 0, 0);
/*
* construct the command to be executed
*/
dp = xlogRecoveryEndCmd;
endp = xlogRecoveryEndCmd + MAXPGPATH - 1;
*endp = '\0';
for (sp = recoveryEndCommand; *sp; sp++)
{
if (*sp == '%')
{
switch (sp[1])
{
case 'r':
/* %r: filename of last restartpoint */
sp++;
StrNCpy(dp, lastRestartPointFname, endp - dp);
dp += strlen(dp);
break;
case '%':
/* convert %% to a single % */
sp++;
if (dp < endp)
*dp++ = *sp;
break;
default:
/* otherwise treat the % as not special */
if (dp < endp)
*dp++ = *sp;
break;
}
}
else
{
if (dp < endp)
*dp++ = *sp;
}
}
*dp = '\0';
ereport(DEBUG3,
(errmsg_internal("executing recovery end command \"%s\"",
xlogRecoveryEndCmd)));
/*
* Copy xlog from archival storage to XLOGDIR
*/
rc = system(xlogRecoveryEndCmd);
if (rc != 0)
{
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See also detailed comments on signals in
* RestoreArchivedFile().
*/
signaled = WIFSIGNALED(rc) || WEXITSTATUS(rc) > 125;
ereport(signaled ? FATAL : WARNING,
(errmsg("recovery_end_command \"%s\": return code %d",
xlogRecoveryEndCmd, rc)));
}
}
/* /*
* Preallocate log files beyond the specified log endpoint. * Preallocate log files beyond the specified log endpoint.
* *
...@@ -4664,6 +4774,13 @@ readRecoveryCommandFile(void) ...@@ -4664,6 +4774,13 @@ readRecoveryCommandFile(void)
(errmsg("restore_command = '%s'", (errmsg("restore_command = '%s'",
recoveryRestoreCommand))); recoveryRestoreCommand)));
} }
else if (strcmp(tok1, "recovery_end_command") == 0)
{
recoveryEndCommand = pstrdup(tok2);
ereport(LOG,
(errmsg("recovery_end_command = '%s'",
recoveryEndCommand)));
}
else if (strcmp(tok1, "recovery_target_timeline") == 0) else if (strcmp(tok1, "recovery_target_timeline") == 0)
{ {
rtliGiven = true; rtliGiven = true;
...@@ -5622,6 +5739,9 @@ StartupXLOG(void) ...@@ -5622,6 +5739,9 @@ StartupXLOG(void)
* allows some extra error checking in xlog_redo. * allows some extra error checking in xlog_redo.
*/ */
CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE); CreateCheckPoint(CHECKPOINT_IS_SHUTDOWN | CHECKPOINT_IMMEDIATE);
if (recoveryEndCommand)
ExecuteRecoveryEndCommand();
} }
/* /*
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment