From: Simon Riggs <simon@2ndquadrant.com>
To: markw@osdl.org
Cc: pgman@candle.pha.pa.us
Cc: kn@mgnet.de
Cc: tgl@sss.pgh.pa.us
Cc: pgsql-patches@postgresql.org
Subject: Re: [HACKERS] Point in Time Recovery
Date: Wed, 14 Jul 2004 23:56:39 +0100
Message-ID: <1089845797.17493.4987.camel@stromboli> (raw)
In-Reply-To: <1089833620.17493.4624.camel@stromboli>
References: <200407141555.i6EFtnk22984@mail.osdl.org>
<1089833620.17493.4624.camel@stromboli>
On Wed, 2004-07-14 at 20:33, Simon Riggs wrote:
> On Wed, 2004-07-14 at 16:55, markw@osdl.org wrote:
> > On 14 Jul, Simon Riggs wrote:
> > > PITR Patch v5_1 just posted has Point in Time Recovery working....
> > >
> > > Still some rough edges....but we really need some testers now to give
> > > this a try and let me know what you think.
> > >
> > > Klaus Naumann and Mark Wong are the only [non-committers] to have tried
> > > to run the code (and let me know about it), so please have a look at
> > > [PATCHES] and try it out.
> > >
>
> > I just tried applying the v5_1 patch against the cvs tip today and got a
> > couple of rejections. I'll copy the patch output here. Let me know if
> > you want to see the reject files or anything else:
> >
>
> I'm on it. Sorry 'bout that all - midnight fingers.
Latest version, pitr_v5_2.patch...
- Updated to cvs tip
- Additional tip changes located and patched
- Full re-test of both recover to point in time and recover to xid
- 2 additional bug fixes
- corrected recovery.conf sample
- Patch test
- Patch manually inspected
(pgarch.c, pgarch.h and README identical to previous post)
Go for it...
Best regards, Simon
Attachments:pitr_v5_2.patch (text/x-patch, 57.9K) download
Index: backend/access/nbtree/nbtsort.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/access/nbtree/nbtsort.c,v
retrieving revision 1.83
diff -c -r1.83 nbtsort.c
*** backend/access/nbtree/nbtsort.c 11 Jul 2004 18:01:45 -0000 1.83
--- backend/access/nbtree/nbtsort.c 14 Jul 2004 22:41:19 -0000
***************
*** 67,72 ****
--- 67,73 ----
#include "miscadmin.h"
#include "storage/smgr.h"
#include "utils/tuplesort.h"
+ #include "access/xlog.h"
/*
***************
*** 220,235 ****
wstate.index = btspool->index;
/*
! * We need to log index creation in WAL iff WAL archiving is enabled
* AND it's not a temp index.
- *- * XXX when WAL archiving is actually supported, this test will likely- * need to change; and the hardwired extern is cruddy anyway ...
*/
{
! extern char XLOG_archive_dir[];
!
! wstate.btws_use_wal = XLOG_archive_dir[0] && !wstate.index->rd_istemp;
}
/* reserve the metapage */
wstate.btws_pages_alloced = BTREE_METAPAGE + 1;
--- 221,231 ----
wstate.index = btspool->index;
/*
! * We need to log index creation in WAL if WAL archiving is enabled
* AND it's not a temp index.
*/
{
! wstate.btws_use_wal = XLogArchiveMode && !wstate.index->rd_istemp;
}
/* reserve the metapage */
wstate.btws_pages_alloced = BTREE_METAPAGE + 1;
Index: backend/access/transam/xlog.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/access/transam/xlog.c,v
retrieving revision 1.147
diff -c -r1.147 xlog.c
*** backend/access/transam/xlog.c 1 Jul 2004 00:49:50 -0000 1.147
--- backend/access/transam/xlog.c 14 Jul 2004 22:41:24 -0000
***************
*** 36,47 ****
#include "storage/proc.h"
#include "storage/sinval.h"
#include "storage/spin.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/relcache.h"
#include "miscadmin.h"
-
/*
* This chunk of hackery attempts to determine which file sync methods
* are available on the current platform, and to choose an appropriate
--- 36,47 ----
#include "storage/proc.h"
#include "storage/sinval.h"
#include "storage/spin.h"
+ #include "storage/pmsignal.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/relcache.h"
#include "miscadmin.h"
/*
* This chunk of hackery attempts to determine which file sync methods
* are available on the current platform, and to choose an appropriate
***************
*** 85,96 ****
/* User-settable parameters */
int CheckPointSegments = 3;
int XLOGbuffers = 8;
char *XLOG_sync_method = NULL;
const char XLOG_sync_method_default[] = DEFAULT_SYNC_METHOD_STR;
- char XLOG_archive_dir[MAXPGPATH]; /* null string means- * delete 'em */
#ifdef WAL_DEBUG
bool XLOG_DEBUG = false;
--- 85,98 ----
/* User-settable parameters */
+ bool XLogArchiveMode = false;+ bool XLogArchiveDEBUG = false;+ char *XLogArchiveDest;+ char *XLogArchiveProgram;
int CheckPointSegments = 3;
int XLOGbuffers = 8;
char *XLOG_sync_method = NULL;
const char XLOG_sync_method_default[] = DEFAULT_SYNC_METHOD_STR;
#ifdef WAL_DEBUG
bool XLOG_DEBUG = false;
***************
*** 127,132 ****
--- 129,157 ----
/* Are we doing recovery by reading XLOG? */
bool InRecovery = false;
+ bool InArchiveRecovery = false;+ bool restoreFromArchive = false;+ bool InRecoveryCleanup = false;+ + FILE *archiveStatusLogFD;+ + static void readRecoveryCommandFile(void);+ + static char recoveryCommandFile[MAXPGPATH];+ + static char recoveryRestoreProgram[MAXPGPATH];+ static char recoveryTargetTimeStr[MAXPGPATH];+ static char recoveryTargetXidStr[MAXPGPATH];+ + static bool recoveryTarget = true;+ static bool recoveryTargetExact = false;+ static bool recoveryTargetInclusive = true;+ + static bool recoveryDebugLog = false;+ static bool recoveryUseLocalTime = true;+ + static TransactionId recoveryTargetXid;+ static time_t recoveryTargetTime;
/*
* MyLastRecPtr points to the start of the last XLOG record inserted by the
***************
*** 393,398 ****
--- 418,424 ----
/* File path names */
static char XLogDir[MAXPGPATH];
+ static char archiveStatusDir[MAXPGPATH];
static char ControlFilePath[MAXPGPATH];
/*
***************
*** 434,439 ****
--- 460,468 ----
static bool InRedo = false;
+ static void XLogArchiveNotify(uint32 log, uint32 seg);+ static bool XLogArchiveDone(char xlog[MAXPGPATH]);+ static void XLogArchiveCleanup(char xlog[32]);
static bool AdvanceXLInsertBuffer(void);
static bool WasteXLInsertBuffer(void);
***************
*** 444,449 ****
--- 473,479 ----
bool find_free, int max_advance,
bool use_lock);
static int XLogFileOpen(uint32 log, uint32 seg, bool econt);
+ static void RestoreRecoveryXlog(char *path, uint32 log, uint32 seg);
static void PreallocXlogFiles(XLogRecPtr endptr);
static void MoveOfflineLogs(uint32 log, uint32 seg, XLogRecPtr endptr);
static XLogRecord *ReadRecord(XLogRecPtr *RecPtr, int emode, char *buffer);
***************
*** 455,464 ****
static void ReadControlFile(void);
static char *str_time(time_t tnow);
static void issue_xlog_fsync(void);
- #ifdef WAL_DEBUG
static void xlog_outrec(char *buf, XLogRecord *record);
- #endif-
/*
* Insert an XLOG record having the specified RMID and info bytes,
--- 485,491 ----
***************
*** 912,917 ****
--- 939,1076 ----
}
/*
+ * XLogArchiveNotify+ *+ * Writes an archive notification file to the archiveStatusDir+ *+ * The name of the notification file is the message that will be picked up+ * by the archiver, e.g. we write archiveStatusDir/00000001000000C6.ready + * and the archiver then knows to archive XLogDir/00000001000000C6,+ * then when complete, rename it to archiveStatusDir/00000001000000C6.done+ *+ * Called only when in XLogArchiveMode by one backend process+ * or by Archive Restore in various situations+ */+ static void + XLogArchiveNotify(uint32 log, uint32 seg)+ {+ char archiveStatusLog[32];+ char archiveStatusLogPath[MAXPGPATH];+ + /* insert an otherwise empty file called <XLOG>.ready */+ sprintf(archiveStatusLog, "%08X%08X.ready", log, seg);+ snprintf(archiveStatusLogPath, MAXPGPATH, "%s/%s", archiveStatusDir, archiveStatusLog);+ archiveStatusLogFD = NULL;+ archiveStatusLogFD = AllocateFile(archiveStatusLogPath, "w");+ if (archiveStatusLogFD == NULL) {+ ereport(ERROR,+ (errcode_for_file_access(),+ errmsg("could not write archive_status file \"%s\": %m",+ archiveStatusLogPath)));+ return;+ }+ FreeFile(archiveStatusLogFD);+ + /* the existence of this file is the message to the archiver to identify+ * which files require archiving+ *+ * if this file is written OK, we then signal the ARCHIVER to do its thang+ */+ + if (XLogArchiveDEBUG) {+ sprintf(archiveStatusLog, "%08X%08X", log, seg);+ ereport(LOG,+ (errmsg("transaction log file \"%s\" is now ready to be archived", archiveStatusLog)));+ }+ + /*+ * don't send a signal if we know that the archiver isn't there (yet)+ * - the archiver will see the archive_status file as soon as it starts + */+ if (!InArchiveRecovery)+ SendPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER);+ + return;+ }+ + /*+ * XLogArchiveDone+ *+ * Searches for an archive notification file in archiveStatusDir+ * + * Reads archiveStatusDir looking for a specific filename. If that filename ends with .done+ * then we know that the filename refers to an xlog in XLogDir that is safe to+ * recycle. If the filename ends .ready then thats OK, else we have an error.+ * + * Called only when in XLogArchiveMode by bgwriter (when performing checkpoint)+ *+ * XXX code is rehacked from an earlier version, so needs streamlining+ */+ static bool + XLogArchiveDone(char xlog[32])+ {+ char archiveStatusLogPath[MAXPGPATH];+ struct stat tmpStatBuf;+ + /* If <XLOG>.done exists then return true+ */+ snprintf(archiveStatusLogPath, MAXPGPATH, "%s/%s.done", archiveStatusDir, xlog);+ if (stat(archiveStatusLogPath, &tmpStatBuf) == 0) {+ return true;+ } + else+ {+ /*+ * else if <XLOG>.ready exists then return false and issue WARNING+ * ...this indicates archiver is either not working at all or+ * if it is, then its just way too slow or incorrectly configured+ */+ snprintf(archiveStatusLogPath, MAXPGPATH, "%s/%s.ready", archiveStatusDir, xlog);+ if (stat(archiveStatusLogPath, &tmpStatBuf) == 0) {+ ereport(WARNING,+ (errcode(ERRCODE_WARNING),+ errmsg("archiving of log file \"%s\" has not yet started", + archiveStatusLogPath),+ errhint("you should check whether your archive_program"+ "\"%s\" is still functioning correctly", XLogArchiveProgram)));+ return false;+ }+ else+ {+ /* else issue a WARNING.... a notification file should exist, except when you+ * have just enabled archive_mode+ * assume that error is simply a missing file...+ */ + ereport(WARNING,+ (errcode_for_file_access(),+ errmsg("archive_status file \"%s\" is missing: %m",+ archiveStatusLogPath),+ errhint("if you have just enabled archive_mode you should:"+ "take a full physical backup, then remove \"%s\"",xlog)));+ return false;+ }+ }+ }+ + /*+ * XLogArchiveCleanup+ *+ * Cleanup an archive notification file for a particular xlog in XLogDir+ * + * Called only when in XLogArchiveMode by bgwriter (when performing checkpoint)+ *+ */+ static void+ XLogArchiveCleanup(char xlog[32])+ {+ char archiveStatusLogPath[MAXPGPATH];+ + snprintf(archiveStatusLogPath, MAXPGPATH, "%s/%s.done", archiveStatusDir, xlog);+ unlink(archiveStatusLogPath);+ + }+ + /*
* Advance the Insert state to the next buffer page, writing out the next
* buffer if it still contains unwritten data.
*
***************
*** 1260,1265 ****
--- 1419,1430 ----
{
issue_xlog_fsync();
LogwrtResult.Flush = LogwrtResult.Write; /* end of current page */
+ + /* + * write archive_status file to notify that xlog is ready to archive+ */+ if (XLogArchiveMode)+ XLogArchiveNotify(openLogId, openLogSeg);
}
if (ispartialpage)
***************
*** 1619,1625 ****
bool use_lock)
{
char path[MAXPGPATH];
! struct stat stat_buf;
XLogFileName(path, log, seg);
--- 1784,1790 ----
bool use_lock)
{
char path[MAXPGPATH];
! struct stat tmpStatBuf;
XLogFileName(path, log, seg);
***************
*** 1637,1643 ****
else
{
/* Find a free slot to put it in */
! while (stat(path, &stat_buf) == 0)
{
if (--max_advance < 0)
{
--- 1802,1808 ----
else
{
/* Find a free slot to put it in */
! while (stat(path, &tmpStatBuf) == 0)
{
if (--max_advance < 0)
{
***************
*** 1686,1692 ****
char path[MAXPGPATH];
int fd;
! XLogFileName(path, log, seg);
fd = BasicOpenFile(path, O_RDWR | PG_BINARY | XLOG_SYNC_BIT,
S_IRUSR | S_IWUSR);
--- 1851,1859 ----
char path[MAXPGPATH];
int fd;
! XLogFileName(path, log, seg);
! if (restoreFromArchive)
! RestoreRecoveryXlog(path, log, seg);
fd = BasicOpenFile(path, O_RDWR | PG_BINARY | XLOG_SYNC_BIT,
S_IRUSR | S_IWUSR);
***************
*** 1706,1715 ****
--- 1873,2104 ----
path, log, seg)));
}
+ /*+ * if we switched back to local xlogs after having been+ * restoring from archive, we need to make sure that the+ * local files dont get removed by end-of-recovery checkpoint+ * in case we need to re-run the recovery+ *+ * we want to copy these away as soon as possible, so set+ * the archive_status flag to .ready for them+ * in case admin isn't cautious enough to have done this anyway+ */+ if (InArchiveRecovery && !restoreFromArchive) {+ snprintf(path, MAXPGPATH, "%s/%08X%08X.ready", archiveStatusDir, log, seg);+ archiveStatusLogFD = NULL;+ archiveStatusLogFD = AllocateFile(path, "w");+ if (archiveStatusLogFD == NULL)+ ereport(ERROR,+ (errcode_for_file_access(),+ errmsg("could not write archive_status file \"%s\" ",+ path)));+ FreeFile(archiveStatusLogFD);+ } +
return (fd);
}
/*
+ * Get next logfile segment to allow recovery+ *+ */+ static void+ RestoreRecoveryXlog(char *path, uint32 log, uint32 seg)+ {+ char tmpXlog[32];+ char restoreXlog[32];+ char tmppath[MAXPGPATH];+ char xlogRestoreCmd[MAXPGPATH];+ char recoveryXlog[MAXPGPATH];+ char lastrecoXlog[MAXPGPATH];+ int rc;+ struct stat tmpStatBuf;+ uint32 prevlog, prevseg;+ + /* + * If a RecoveryFile exists, then we know we are in media recovery+ * in which case we choose to recover files from archive, even+ * though a file of that name may already exist in XLogDir+ *+ * By doing this, we do not effect crash recovery code path+ * when we are not in archive_mode+ *+ * We take the archived file because, at the point we took backup,+ * the current xlog will most probably be only partially full, + * so we MUST refer to the full version of this file and + * NOT the version of the file that exists with the backup.+ *+ * We could try to optimize this slightly by checking the local+ * copy lastchange timestamp against the archived copy, + * but we have no API to do this, nor can we guarantee that the+ * lastchange timestamp was preserved correctly when we copied+ * to archive. Our aim is robustness, so we elect not to do this.+ *+ * Try to copy full xlog from archive to pg_xlog, if it is available+ * If that succeeds, we pass the RecoveryXlog filepath back for opening+ * If that fails, then we try to read a local file if one exists.+ * This allows us to cater for situations where the current xlog+ * is still available locally and hasn't yet made it to archive.+ * This could happen if:+ * - we decide to recover database to undo user data changes+ * - we have XLogDir on a different disk and the main DataDir drive+ * fails, leaving us with just the XLogDir+ *+ * Notice that we don't actually overwrite any files when we copy back+ * from archive because the recoveryRestoreProgram may inadvertently+ * restore inappropriate xlogs, or they may be corrupt, so we may+ * have to fallback to the segments remaining in current XLogDir later.+ * The copy-from-archive xlog is always the same, ensuring that we+ * don't run out of disk space on long recoveries.+ *+ * simon@2ndquadrant.com+ */+ + snprintf(recoveryXlog, MAXPGPATH, "%s/RECOVERYXLOG", XLogDir);+ snprintf(lastrecoXlog, MAXPGPATH, "%s/LASTRECOXLOG", XLogDir);+ + if (stat(recoveryXlog, &tmpStatBuf) == 0) { + /*+ * save a copy of the last xlog, before we try to restore+ * if the restore fails, we will need it to become current xlog+ */+ rc = rename(recoveryXlog, lastrecoXlog);+ if (rc !=0)+ ereport(WARNING,+ (errcode_for_file_access(),+ errmsg("rename failed \"%s\" \"%s\":%m",+ recoveryXlog, lastrecoXlog)));+ /*+ * if it fails, ignore it - we'll create one soon...+ */+ }+ + /*+ * Copy xlog from archive_dest to XLogDir+ */+ sprintf(restoreXlog, "%08X%08X", log, seg);+ snprintf(xlogRestoreCmd, MAXPGPATH, recoveryRestoreProgram, + XLogArchiveDest, restoreXlog, recoveryXlog);+ if (XLogArchiveDEBUG)+ ereport(LOG,+ (errmsg("restore_program=\"%s\"",+ xlogRestoreCmd)));+ + rc = system(xlogRestoreCmd);+ if (rc!=0) {+ /*+ * remember, we rollforward UNTIL the restore fails+ * so failure here is just part of the process...+ * that makes it difficult to determine whether the restore+ * failed because there isn't an archive to restore, or+ * because the administrator has specified the restore+ * program incorrectly...+ * we could try to restore the testfile that the archiver writes+ * when it starts up, but the absence of that file isn't+ * very reliable evidence that the restore itself is broken,+ * so just trust that the administrator has it correctly,+ * XXX enhance that later+ */+ ereport(LOG,+ (errmsg("could not restore \"%s\" from archive",+ restoreXlog)));+ /*+ * if an archived file is not available, there might just be + * a partially full version of this file still in XLogDir+ * so return this as the filename to open.+ * In many recovery scenarios we expect this to fail also...+ * ...note that once we return to local mode, we could+ * find more than one xlog there that were not in the archive,+ * if for example, the archiver had stopped working prior+ * to the event which led to recovery+ */+ snprintf(recoveryXlog, MAXPGPATH, "%s/%s", XLogDir, restoreXlog);+ restoreFromArchive = false;+ if (stat(recoveryXlog, &tmpStatBuf) == 0) {+ ereport(LOG,+ (errmsg("recovery will continue using local copy of \"%s\"",+ restoreXlog)));+ }+ /*+ * if this file isn't available, then we need to setup the previous+ * restored xlog to be the last and current xlog, if it exists+ * remember: we've been restoring from recoverXlog, which isn't+ * named the same as the normal xlog chain...+ * also remember to output a corresponding archive_status of .done+ */+ else if ((stat(lastrecoXlog, &tmpStatBuf) == 0) && log==0 && seg > 0) {+ prevlog = log;+ prevseg = seg;+ PrevLogSeg(prevlog, prevseg);+ XLogFileName(tmppath, prevlog, prevseg);+ if (XLogArchiveDEBUG)+ ereport(LOG,+ (errmsg("moving last restored xlog to \"%s\"", tmppath)));+ rc = rename(lastrecoXlog, tmppath);+ if (rc!=0) {+ ereport(LOG,+ (errcode_for_file_access(),+ errmsg("rename failed \"%s\" \"%s\":%m",+ lastrecoXlog, tmppath)));+ ereport(PANIC,+ (errcode_for_file_access(),+ errmsg("could not open file \"%s\" (log file %u, segment %u): %m",+ tmpXlog, log, seg)));+ }+ + /* + * write out an archive_status file for previous xlog+ * to allow xlog to be recycled when recovered database+ * is all up and working again+ * ...looks wrong, but checkpointer is smart enough+ * not to archive the current xlog!+ */+ sprintf(tmpXlog, "%08X%08X", prevlog, prevseg);+ snprintf(tmppath, MAXPGPATH, "%s/%s.done", archiveStatusDir, tmpXlog);+ archiveStatusLogFD = NULL;+ archiveStatusLogFD = AllocateFile(tmppath, "w");+ if (archiveStatusLogFD == NULL)+ ereport(ERROR,+ (errcode_for_file_access(),+ errmsg("could not write archive_status file \"%s\" ",+ tmppath)));+ FreeFile(archiveStatusLogFD);+ }+ /* + * there is NO else here...we just return the filename+ * knowing that it isn't there...which then throws the usual error,+ * will end with a clear message as to why...but not a problem+ */+ }+ else {+ /* restore success */+ /* + * if backup restored an xlog, yet we didnt use the local copy+ * because we used the xlog version of that name from the+ * archive instead, we need to write out an archive_status for+ * it to show it can be recycled later+ */+ XLogFileName(tmppath, log, seg);+ if (stat(tmppath, &tmpStatBuf) == 0) {+ sprintf(tmpXlog, "%08X%08X", log, seg);+ snprintf(tmppath, MAXPGPATH, "%s/%s.done", archiveStatusDir, tmpXlog);+ archiveStatusLogFD = NULL;+ archiveStatusLogFD = AllocateFile(tmppath, "w");+ if (archiveStatusLogFD == NULL)+ ereport(ERROR,+ (errcode_for_file_access(),+ errmsg("could not write archive_status file \"%s\": %m",+ tmppath)));+ FreeFile(archiveStatusLogFD);+ }+ ereport(LOG,+ (errmsg("restored log file \"%s\" from archive", restoreXlog)));+ }+ strcpy(path, recoveryXlog); + return;+ }+ + /*
* Preallocate log files beyond the specified log endpoint, according to
* the XLOGfile user parameter.
*/
***************
*** 1747,1752 ****
--- 2136,2142 ----
struct dirent *xlde;
char lastoff[32];
char path[MAXPGPATH];
+ bool recycle=false;
XLByteToPrevSeg(endptr, endlogId, endlogSeg);
***************
*** 1762,1786 ****
errno = 0;
while ((xlde = readdir(xldir)) != NULL)
{
if (strlen(xlde->d_name) == 16 &&
strspn(xlde->d_name, "0123456789ABCDEF") == 16 &&
strcmp(xlde->d_name, lastoff) <= 0)
{
snprintf(path, MAXPGPATH, "%s/%s", XLogDir, xlde->d_name);
! if (XLOG_archive_dir[0])
! {
! ereport(LOG,
! (errmsg("archiving transaction log file \"%s\"",
! xlde->d_name)));
! elog(WARNING, "archiving log files is not implemented");
! }
! else
{
/*
* Before deleting the file, see if it can be recycled as
* a future log segment. We allow recycling segments up
! * to XLOGfileslop segments beyond the current XLOG
! * location.
*/
if (InstallXLogFileSegment(endlogId, endlogSeg, path,
true, XLOGfileslop,
--- 2152,2194 ----
errno = 0;
while ((xlde = readdir(xldir)) != NULL)
{
+ /* if correct length and alphanumeric makeup of file looks correct+ * use the alphanumeric sorting property of the filenames to decide+ * which ones are earlier than the lastoff transaction log+ * ...maybe should read lastwrite datetime of lastoff, then check that+ * only files last written earlier than this are removed/recycled+ */
if (strlen(xlde->d_name) == 16 &&
strspn(xlde->d_name, "0123456789ABCDEF") == 16 &&
strcmp(xlde->d_name, lastoff) <= 0)
{
snprintf(path, MAXPGPATH, "%s/%s", XLogDir, xlde->d_name);
! if (XLogArchiveMode) {
! if (InRecoveryCleanup)
! /*
! * this allows recycling of transaction logs
! * during the shutdown checkpoint at end of recovery
! * - we may have restored logs that were not used
! * in the recovery sequence, and so will not have
! * had an archive_status file written for them.
! * - end-of-recovery doesn't clean up ALL xlogs,
! * which is why we also write archive_status files
! * as well as doing this
! */
! recycle=true;
! else
! recycle=XLogArchiveDone(xlde->d_name);
! }
! else
! recycle=false;
!
! if ( recycle )
{
/*
* Before deleting the file, see if it can be recycled as
* a future log segment. We allow recycling segments up
! * until there are XLOGfileslop segments beyond the
! * current XLOG location, otherwise they are removed.
*/
if (InstallXLogFileSegment(endlogId, endlogSeg, path,
true, XLOGfileslop,
***************
*** 1794,1803 ****
{
/* No need for any more future segments... */
ereport(LOG,
! (errmsg("removing transaction log file \"%s\"",
xlde->d_name)));
unlink(path);
}
}
}
errno = 0;
--- 2202,2212 ----
{
/* No need for any more future segments... */
ereport(LOG,
! (errmsg("too many transaction log files, removing \"%s\"",
xlde->d_name)));
unlink(path);
}
+ XLogArchiveCleanup(xlde->d_name);
}
}
errno = 0;
***************
*** 2255,2260 ****
--- 2664,2670 ----
{
/* Init XLOG file paths */
snprintf(XLogDir, MAXPGPATH, "%s/pg_xlog", DataDir);
+ snprintf(archiveStatusDir, MAXPGPATH, "%s/archive_status", XLogDir);
snprintf(ControlFilePath, MAXPGPATH, "%s/global/pg_control", DataDir);
}
***************
*** 2772,2777 ****
--- 3182,3345 ----
}
/*
+ * read in restore command from recovery.conf+ *+ * XXX longer term intention is to expand this to + * cater for additional parameters and controls+ * possibly using a bison grammar to control it+ */+ static void+ readRecoveryCommandFile(void)+ {+ FILE *fd;+ char *tok1 = NULL;+ char *tok2 = NULL;+ char cmdline[MAXPGPATH];+ bool syntaxError = false;+ struct tm tm;+ + fd = AllocateFile(recoveryCommandFile, "r");+ if (fd == NULL) {+ ereport(FATAL,+ (errcode_for_file_access(),+ errmsg("could not open recovery command file \"%s\"",recoveryCommandFile)));+ return;+ }+ + ereport(LOG,+ (errmsg("recovery command file found...")));+ + /* + * expecting |restore_program = Qcommand stringQ|+ * e.g. |restore_program = 'cp %s/%s %s'|+ * where | denote the beginning and end of the string+ */+ while (!syntaxError && fgets(cmdline, MAXPGPATH, fd) != NULL) {+ + /* ignore # comments ... */+ if(strncmp(cmdline, "#", 1) == 0) continue;+ + tok1 = strtok(cmdline, "'");+ tok2 = strtok(NULL, "'");+ + if (tok1 != NULL && tok2 != NULL) {+ tok1 = strtok(cmdline, " =");+ if (tok1 == NULL) {+ syntaxError = true;+ continue;+ }+ if (strcmp(tok1,"restore_program") == 0) {+ strcpy(recoveryRestoreProgram, tok2);+ ereport(LOG,+ (errmsg("restore_program = %s", recoveryRestoreProgram)));+ recoveryTarget = false;+ }+ else if (strcmp(tok1,"recovery_target_xid") == 0) {+ /* + * if recovery_target_xid specified, then this overrides+ * recovery_target_time, if it is also set+ */+ strcpy(recoveryTargetXidStr, tok2);+ recoveryTargetXid = (TransactionId) strtoul(tok2, NULL, 10);+ if (errno == EINVAL || errno == ERANGE ) {+ ereport(FATAL,+ (errmsg("recovery_target_xid is not a valid xid \"%s\"",+ recoveryTargetTimeStr)));+ }+ ereport(LOG,(errmsg("recovery_target_xid = %s", recoveryTargetXidStr)));+ recoveryTarget = true;+ recoveryTargetExact = true;+ }+ else if (strcmp(tok1,"recovery_target_time") == 0) {+ if (recoveryTargetExact)+ continue;+ strcpy(recoveryTargetTimeStr, tok2);+ recoveryTarget = true;+ recoveryTargetExact = false;+ /* + * convert the time string given+ * by the user to the time_t format.+ * The input string format has to be like + * '2004-07-13-16:09:07'.+ */+ if (strptime(recoveryTargetTimeStr, + "%Y-%m-%d-%H:%M:%S", &tm) == NULL ) {+ ereport(FATAL,+ (errmsg("recovery_target_time has a format error \"%s\"",+ recoveryTargetTimeStr),+ errhint("correct format is YYYY-MM-DD-hh:mm:ss")));+ }+ recoveryTargetTime = mktime(&tm);+ if (recoveryTargetTime < 0) {+ ereport(FATAL,+ (errmsg("recovery_target_time is an invalid time \"%s\"",+ recoveryTargetTimeStr),+ errhint("correct format is YYYY-MM-DD-hh:mm:ss")));+ }+ ereport(LOG,(errmsg("recovery_target_time = %s", + recoveryTargetTimeStr)));+ }+ else if (strcmp(tok1,"recovery_target_inclusive") == 0) {+ /* + * does nothing if a recovery_target is not also set+ */+ if (strcmp(tok2, "true") == 0)+ recoveryTargetInclusive = true;+ else+ recoveryTargetInclusive = false;+ if (recoveryTargetInclusive)+ strcpy(tok2, "true");+ else+ strcpy(tok2, "false");+ ereport(LOG,(errmsg("recovery_target_inclusive = %s", tok2)));+ }+ else if (strcmp(tok1,"recovery_debug_log") == 0) {+ if (strcmp(tok2, "true") == 0)+ recoveryDebugLog = true;+ else+ recoveryDebugLog = false;+ if (recoveryDebugLog)+ strcpy(tok2, "true");+ else+ strcpy(tok2, "false");+ ereport(LOG,(errmsg("recovery_debug_log = %s", tok2)));+ }+ else if (strcmp(tok1,"use_local_time") == 0) {+ if (strcmp(tok2, "true") == 0)+ recoveryUseLocalTime = true;+ else+ recoveryUseLocalTime = false;+ if (recoveryUseLocalTime)+ strcpy(tok2, "true");+ else+ strcpy(tok2, "false");+ ereport(LOG,(errmsg("use_local_time = %s", tok2)));+ }+ else {+ ereport(LOG,+ (errmsg("unrecognized parameter \"%s\"", tok1)));+ syntaxError = true; + }+ }+ else {+ ereport(LOG,+ (errmsg("incorrectly formatted parameter \"%s\"", cmdline),+ errhint("parameter = 'value'")));+ syntaxError = true; + }+ }+ + FreeFile(fd);+ + if (syntaxError) {+ ereport(FATAL,+ (errmsg("fatal error in \"%s\"", recoveryCommandFile)));+ }+ + return;+ }+ + /*
* This must be called ONCE during postmaster or standalone-backend startup
*/
void
***************
*** 2787,2792 ****
--- 3355,3361 ----
XLogRecord *record;
char *buffer;
uint32 freespace;
+ struct stat tmpStatBuf;
/* Use malloc() to ensure record buffer is MAXALIGNED */
buffer = (char *) malloc(_INTL_MAXLOGRECSZ);
***************
*** 2833,2838 ****
--- 3402,3437 ----
pg_usleep(60000000L);
#endif
+ /*+ * Check now for recovery.conf+ *+ * if this file exists, it demonstrates the intention of the administrator+ * to recover this database using archived xlogs+ *+ * we do this now because the first xlog is about to be opened for the+ * first time. We've read the checkpoint pointer from the control file+ * and we are about to use that to open the xlog it points to, and+ * will begin rollforward recovery from that point+ */+ snprintf(recoveryCommandFile, MAXPGPATH, "%s/recovery.conf", DataDir);+ if (stat(recoveryCommandFile, &tmpStatBuf) == 0) {+ + readRecoveryCommandFile();+ /*+ * clearly indicate our state+ */+ InArchiveRecovery = true;+ /*+ * set initial state for checking transaction logs+ * this may change if the archive runs dry while still InArchiveRecovery+ */+ restoreFromArchive = true;+ + ereport(LOG,+ (errmsg("starting archive recovery")));+ + }+
/*
* Get the last valid checkpoint record. If the latest one according
* to pg_control is broken, try the next-to-last one.
***************
*** 2863,2874 ****
LastRec = RecPtr = checkPointLoc;
memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
wasShutdown = (record->xl_info == XLOG_CHECKPOINT_SHUTDOWN);
!
ereport(LOG,
(errmsg("redo record is at %X/%X; undo record is at %X/%X; shutdown %s",
checkPoint.redo.xlogid, checkPoint.redo.xrecoff,
checkPoint.undo.xlogid, checkPoint.undo.xrecoff,
! wasShutdown ? "TRUE" : "FALSE")));
ereport(LOG,
(errmsg("next transaction ID: %u; next OID: %u",
checkPoint.nextXid, checkPoint.nextOid)));
--- 3462,3481 ----
LastRec = RecPtr = checkPointLoc;
memcpy(&checkPoint, XLogRecGetData(record), sizeof(CheckPoint));
wasShutdown = (record->xl_info == XLOG_CHECKPOINT_SHUTDOWN);
! /*
! * we report the state of the control_file, not the checkpoint, why?
! * wasShutdown refers to whether the last checkpoint was a
! * shutdown checkpoint, NOT whether the database was shutdown
! * correctly according to control file. This distinction is only
! * important InArchiveRecovery, since otherwise we could
! * report that the database was shutdown, when the control file disagrees
! */
ereport(LOG,
(errmsg("redo record is at %X/%X; undo record is at %X/%X; shutdown %s",
checkPoint.redo.xlogid, checkPoint.redo.xrecoff,
checkPoint.undo.xlogid, checkPoint.undo.xrecoff,
! (ControlFile->state == DB_SHUTDOWNED) ? "TRUE" : "FALSE")));
!
ereport(LOG,
(errmsg("next transaction ID: %u; next OID: %u",
checkPoint.nextXid, checkPoint.nextOid)));
***************
*** 2915,2921 ****
/* REDO */
if (InRecovery)
{
! int rmid;
ereport(LOG,
(errmsg("database system was not properly shut down; "
--- 3522,3537 ----
/* REDO */
if (InRecovery)
{
! int rmid;
! char recoveryLogPath[MAXPGPATH];
! bool recoveryContinue = true;
! bool recoveryApply = true;
! int recoveryLogFD = -1;
! char *recbuf = NULL;
! uint8 record_info;
! xl_xact_commit *recordXactCommitData;
! xl_xact_abort *recordXactAbortData;
! time_t recordXtime;
ereport(LOG,
(errmsg("database system was not properly shut down; "
***************
*** 2948,2953 ****
--- 3564,3589 ----
ereport(LOG,
(errmsg("redo starts at %X/%X",
ReadRecPtr.xlogid, ReadRecPtr.xrecoff)));
+ #ifdef WAL_DEBUG+ if (XLOG_DEBUG)+ recoveryDebugLog = true;+ #endif+ if (XLogArchiveDEBUG) + recoveryDebugLog = true;+ + if (recoveryDebugLog) {+ recbuf = (char *) malloc(BLCKSZ);+ snprintf(recoveryLogPath, MAXPGPATH, "%s/recovery.log", DataDir);+ unlink(recoveryLogPath);+ recoveryLogFD = BasicOpenFile(recoveryLogPath, O_RDWR | O_CREAT | O_EXCL,+ S_IRUSR | S_IWUSR);+ if (recoveryLogFD < 0)+ recoveryDebugLog = false;+ }+ + /*+ * main redo apply loop+ */
do
{
/* nextXid must be beyond record's xid */
***************
*** 2958,2990 ****
TransactionIdAdvance(ShmemVariableCache->nextXid);
}
! #ifdef WAL_DEBUG
! if (XLOG_DEBUG)
{
! char buf[8192];
!
! sprintf(buf, "REDO @ %X/%X; LSN %X/%X: ",
ReadRecPtr.xlogid, ReadRecPtr.xrecoff,
EndRecPtr.xlogid, EndRecPtr.xrecoff);
! xlog_outrec(buf, record);
! strcat(buf, " - ");
! RmgrTable[record->xl_rmid].rm_desc(buf,
record->xl_info, XLogRecGetData(record));
! elog(LOG, "%s", buf);
}
- #endif
if (record->xl_info & XLR_BKP_BLOCK_MASK)
RestoreBkpBlocks(record, EndRecPtr);
- RmgrTable[record->xl_rmid].rm_redo(EndRecPtr, record);- record = ReadRecord(NULL, LOG, buffer);- } while (record != NULL);
ereport(LOG,
(errmsg("redo done at %X/%X",
ReadRecPtr.xlogid, ReadRecPtr.xrecoff)));
LastRec = ReadRecPtr;
InRedo = false;
}
else
ereport(LOG,
--- 3594,3729 ----
TransactionIdAdvance(ShmemVariableCache->nextXid);
}
! if (recoveryDebugLog && record->xl_rmid == 1)
{
! sprintf(recbuf, "\nREDO %X/%X;LSN %X/%X: ",
ReadRecPtr.xlogid, ReadRecPtr.xrecoff,
EndRecPtr.xlogid, EndRecPtr.xrecoff);
! sprintf(recbuf + strlen(recbuf), "rm %u;info %u ",
! record->xl_rmid, record->xl_info);
! xlog_outrec(recbuf, record);
! strcat(recbuf, "--");
! RmgrTable[record->xl_rmid].rm_desc(recbuf,
record->xl_info, XLogRecGetData(record));
! write(recoveryLogFD, recbuf, strlen(recbuf));
}
if (record->xl_info & XLR_BKP_BLOCK_MASK)
RestoreBkpBlocks(record, EndRecPtr);
+ /*+ * Have we reached our recovery target?+ * check whether this is the last record we should apply -+ * we can only stop at transaction end record types,+ * COMMIT or ABORT, which are only ever associated+ * with the XLOG resource manager (1)+ */+ if (recoveryTarget == true &&+ record->xl_rmid == 1) {+ + record_info = record->xl_info & ~XLR_INFO_MASK;+ + if (record_info == XLOG_XACT_COMMIT ||+ record_info == XLOG_XACT_ABORT) {+ + if (recoveryTargetExact) {+ /* + * there can be only one transaction end record+ * with this exact transactionid+ *+ * when testing for an xid, we MUST test for + * equality only, since transactions are numbered+ * in the order they start, not the order they+ * complete. A higher numbered xid will complete+ * before you about 50% of the time...+ */+ if (record->xl_xid == recoveryTargetXid) {+ recoveryContinue = false;+ recoveryApply = recoveryTargetInclusive;+ }+ if (!recoveryContinue) {+ ereport(LOG,+ (errmsg("rollforward stopping at commit/abort:"+ " transactionId %u",+ record->xl_xid)));+ }+ }+ else {+ /* + * there can be many transactions that+ * share the same commit time, so+ * we stop after the last one, if we are+ * inclusive, or stop at the first one+ * if we are exclusive+ *+ * timestamp is first part of record body+ */+ if (record_info == XLOG_XACT_COMMIT) {+ recordXactCommitData = + (xl_xact_commit *) XLogRecGetData(record);+ recordXtime = recordXactCommitData->xtime;+ }+ else {+ recordXactAbortData = + (xl_xact_abort *) XLogRecGetData(record);+ recordXtime = recordXactAbortData->xtime;+ }+ + if (recoveryTargetInclusive) {+ if ((unsigned int)(recordXtime) >+ (unsigned int)(recoveryTargetTime))+ recoveryContinue = false;+ /*+ * because we have already just included the+ * records that match (==), and we now want+ * to stop just beyond+ */+ recoveryApply = false;+ }+ else {+ if ((unsigned int)(recordXtime) >=+ (unsigned int)(recoveryTargetTime))+ recoveryContinue = false;+ recoveryApply = false;+ }+ if (!recoveryContinue) {+ struct tm *tm = localtime(&recordXtime);+ ereport(LOG,+ (errmsg("rollforward stopping at commit/abort:"+ " time %04u-%02u-%02u %02u:%02u:%02u",+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,+ tm->tm_hour, tm->tm_min, tm->tm_sec)));+ }+ }+ }+ }+ + if (recoveryContinue || recoveryApply) {+ RmgrTable[record->xl_rmid].rm_redo(EndRecPtr, record);+ record = ReadRecord(NULL, LOG, buffer);+ }+ + } while (record != NULL && recoveryContinue);+ /*+ * end of main redo apply+ */+ + if (recoveryLogFD >= 0) {+ close(recoveryLogFD);+ if (recoveryDebugLog)+ free(recbuf);+ }
ereport(LOG,
(errmsg("redo done at %X/%X",
ReadRecPtr.xlogid, ReadRecPtr.xrecoff)));
LastRec = ReadRecPtr;
InRedo = false;
+ if (InArchiveRecovery) {+ InRecoveryCleanup = true;+ InArchiveRecovery = false;+ /* LastRec.xlogid = LastRec.xlogid + TIMELINE_INCREMENT */+ }+ Assert(restoreFromArchive == false);
}
else
ereport(LOG,
***************
*** 3106,3118 ****
* Note that we write a shutdown checkpoint. This is correct since
* the records following it will use SUI one more than what is
* shown in the checkpoint's ThisStartUpID.
! *
* In case we had to use the secondary checkpoint, make sure that it
* will still be shown as the secondary checkpoint after this
* CreateCheckPoint operation; we don't want the broken primary
! * checkpoint to become prevCheckPoint...
*/
! ControlFile->checkPoint = checkPointLoc;
CreateCheckPoint(true, true);
/*
--- 3845,3864 ----
* Note that we write a shutdown checkpoint. This is correct since
* the records following it will use SUI one more than what is
* shown in the checkpoint's ThisStartUpID.
! */
! ControlFile->checkPoint = checkPointLoc;
! /*
* In case we had to use the secondary checkpoint, make sure that it
* will still be shown as the secondary checkpoint after this
* CreateCheckPoint operation; we don't want the broken primary
! * checkpoint to become prevCheckPoint...unless we have just
! * performed an archive recovery, in which case reset the
! * secondary checkpoint as well...to avoid going back over
! * old ground
*/
! if (InRecoveryCleanup)
! ControlFile->prevCheckPoint = checkPointLoc;
!
CreateCheckPoint(true, true);
/*
***************
*** 3149,3154 ****
--- 3895,3906 ----
* Okay, we're officially UP.
*/
InRecovery = false;
+ if (InRecoveryCleanup) {+ unlink(recoveryCommandFile);+ InRecoveryCleanup = false;+ ereport(LOG,+ (errmsg("archive recovery complete")));+ }
ControlFile->state = DB_IN_PRODUCTION;
ControlFile->time = time(NULL);
***************
*** 3706,3719 ****
strcat(buf, "UNKNOWN");
}
- #ifdef WAL_DEBUG
static void
xlog_outrec(char *buf, XLogRecord *record)
{
int bkpb;
int i;
! sprintf(buf + strlen(buf), "prev %X/%X; xprev %X/%X; xid %u",
record->xl_prev.xlogid, record->xl_prev.xrecoff,
record->xl_xact_prev.xlogid, record->xl_xact_prev.xrecoff,
record->xl_xid);
--- 4458,4470 ----
strcat(buf, "UNKNOWN");
}
static void
xlog_outrec(char *buf, XLogRecord *record)
{
int bkpb;
int i;
! sprintf(buf + strlen(buf), "prv %X/%X;xprv %X/%X;xid %u",
record->xl_prev.xlogid, record->xl_prev.xrecoff,
record->xl_xact_prev.xlogid, record->xl_xact_prev.xrecoff,
record->xl_xid);
***************
*** 3731,3738 ****
sprintf(buf + strlen(buf), ": %s",
RmgrTable[record->xl_rmid].rm_name);
}
- #endif /* WAL_DEBUG */-
/*
* GUC support
--- 4482,4487 ----
Index: backend/commands/tablecmds.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/commands/tablecmds.c,v
retrieving revision 1.119
diff -c -r1.119 tablecmds.c
*** backend/commands/tablecmds.c 11 Jul 2004 23:13:53 -0000 1.119
--- backend/commands/tablecmds.c 14 Jul 2004 22:41:46 -0000
***************
*** 5398,5413 ****
FlushRelationBuffers(rel, 0);
/*
! * We need to log the copied data in WAL iff WAL archiving is enabled
* AND it's not a temp rel.
- *- * XXX when WAL archiving is actually supported, this test will likely- * need to change; and the hardwired extern is cruddy anyway ...
*/
{
! extern char XLOG_archive_dir[];
!
! use_wal = XLOG_archive_dir[0] && !rel->rd_istemp;
}
nblocks = RelationGetNumberOfBlocks(rel);
--- 5398,5408 ----
FlushRelationBuffers(rel, 0);
/*
! * We need to log the copied data in WAL if WAL archiving is enabled
* AND it's not a temp rel.
*/
{
! use_wal = XLogArchiveMode && !rel->rd_istemp;
}
nblocks = RelationGetNumberOfBlocks(rel);
Index: backend/postmaster/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/Makefile,v
retrieving revision 1.15
diff -c -r1.15 Makefile
*** backend/postmaster/Makefile 29 May 2004 22:48:19 -0000 1.15
--- backend/postmaster/Makefile 14 Jul 2004 22:41:46 -0000
***************
*** 12,18 ****
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
! OBJS = postmaster.o bgwriter.o pgstat.o
all: SUBSYS.o
--- 12,18 ----
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
! OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o
all: SUBSYS.o
Index: backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/postmaster.c,v
retrieving revision 1.411
diff -c -r1.411 postmaster.c
*** backend/postmaster/postmaster.c 12 Jul 2004 19:14:56 -0000 1.411
--- backend/postmaster/postmaster.c 14 Jul 2004 22:41:48 -0000
***************
*** 117,123 ****
#include "utils/ps_status.h"
#include "bootstrap/bootstrap.h"
#include "pgstat.h"
!
/*
* List of active backends (or child processes anyway; we don't actually
--- 117,123 ----
#include "utils/ps_status.h"
#include "bootstrap/bootstrap.h"
#include "pgstat.h"
! #include "pgarch.h"
/*
* List of active backends (or child processes anyway; we don't actually
***************
*** 198,203 ****
--- 198,204 ----
/* PIDs of special child processes; 0 when not running */
static pid_t StartupPID = 0,
BgWriterPID = 0,
+ PgArchPID = 0,
PgStatPID = 0;
/* Startup/shutdown state */
***************
*** 1217,1222 ****
--- 1218,1228 ----
kill(BgWriterPID, SIGUSR2);
}
+ /* If we have lost the archiver, try to start a new one */+ if (XLogArchiveMode && PgArchPID == 0 && + StartupPID == 0 && !FatalError && Shutdown == NoShutdown)+ PgArchPID = pgarch_start();+
/* If we have lost the stats collector, try to start a new one */
if (PgStatPID == 0 &&
StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
***************
*** 1821,1826 ****
--- 1827,1835 ----
/* Tell pgstat to shut down too; nothing left for it to do */
if (PgStatPID != 0)
kill(PgStatPID, SIGQUIT);
+ /* Tell pgarch to shut down too; nothing left for it to do */+ if (PgArchPID != 0)+ kill(PgArchPID, SIGQUIT);
break;
case SIGINT:
***************
*** 1865,1870 ****
--- 1874,1882 ----
/* Tell pgstat to shut down too; nothing left for it to do */
if (PgStatPID != 0)
kill(PgStatPID, SIGQUIT);
+ /* Tell pgarch to shut down too; nothing left for it to do */+ if (PgArchPID != 0)+ kill(PgArchPID, SIGQUIT);
break;
case SIGQUIT:
***************
*** 1882,1887 ****
--- 1894,1901 ----
kill(BgWriterPID, SIGQUIT);
if (PgStatPID != 0)
kill(PgStatPID, SIGQUIT);
+ if (PgArchPID != 0)+ kill(PgArchPID, SIGQUIT);
if (DLGetHead(BackendList))
SignalChildren(SIGQUIT);
ExitPostmaster(0);
***************
*** 1971,1978 ****
*/
if (Shutdown > NoShutdown && BgWriterPID != 0)
kill(BgWriterPID, SIGUSR2);
! else if (PgStatPID == 0 && Shutdown == NoShutdown)
! PgStatPID = pgstat_start();
continue;
}
--- 1985,1996 ----
*/
if (Shutdown > NoShutdown && BgWriterPID != 0)
kill(BgWriterPID, SIGUSR2);
! else if (Shutdown == NoShutdown) {
! if (PgStatPID == 0)
! PgStatPID = pgstat_start();
! if (PgArchPID == 0)
! PgArchPID = pgarch_start();
! }
continue;
}
***************
*** 2021,2026 ****
--- 2039,2060 ----
}
/*
+ * Was it the archiver? If so, just try to start a new+ * one; no need to force reset of the rest of the system. (If fail,+ * we'll try again in future cycles of the main loop.)+ */+ if (PgArchPID != 0 && pid == PgArchPID)+ {+ PgArchPID = 0;+ if (exitstatus != 0)+ LogChildExit(LOG, gettext("archiver process"),+ pid, exitstatus);+ if (StartupPID == 0 && !FatalError && Shutdown == NoShutdown)+ PgArchPID = pgarch_start();+ continue;+ }+ + /*
* Else do standard backend child cleanup.
*/
CleanupProc(pid, exitstatus);
***************
*** 2202,2207 ****
--- 2236,2252 ----
kill(PgStatPID, SIGQUIT);
}
+ /* Force a power-cycle of the pgarch processes too */+ /* (Shouldn't be necessary, but just for luck) */+ if (PgArchPID != 0 && !FatalError)+ {+ ereport(DEBUG2,+ (errmsg_internal("sending %s to process %d",+ "SIGQUIT",+ (int) PgArchPID)));+ kill(PgArchPID, SIGQUIT);+ }+
FatalError = true;
}
***************
*** 2951,2956 ****
--- 2996,3013 ----
if (Shutdown <= SmartShutdown)
SignalChildren(SIGUSR1);
}
+ + if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER))+ {+ /*+ * Send SIGUSR1 to ARCHIVER process, to wake it up and begin+ * archiving next transaction log file. Backend should only+ * send if in XLogArchiveMode...+ */+ if (XLogArchiveMode && Shutdown == NoShutdown) {+ kill(PgArchPID,SIGUSR1);+ }+ }
PG_SETMASK(&UnBlockSig);
Index: backend/utils/misc/guc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.219
diff -c -r1.219 guc.c
*** backend/utils/misc/guc.c 12 Jul 2004 02:22:51 -0000 1.219
--- backend/utils/misc/guc.c 14 Jul 2004 22:41:52 -0000
***************
*** 342,347 ****
--- 342,363 ----
static struct config_bool ConfigureNamesBool[] =
{
+ {+ {"archive_mode", PGC_POSTMASTER, WAL_SETTINGS,+ gettext_noop("Enable archiving of full transaction log files to a specified archival destination."),+ NULL+ },+ &XLogArchiveMode,+ false, NULL, NULL+ },+ {+ {"archive_debug", PGC_SIGHUP, WAL_SETTINGS,+ gettext_noop("Provide debug output for archive activities."),+ NULL+ },+ &XLogArchiveDEBUG,+ false, NULL, NULL+ },
{
{"enable_seqscan", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of sequential-scan plans."),
***************
*** 1371,1376 ****
--- 1387,1410 ----
static struct config_string ConfigureNamesString[] =
{
+ {+ {"archive_dest", PGC_POSTMASTER, WAL_SETTINGS,+ gettext_noop("Specifies where to archive WAL logs."),+ gettext_noop("A directory or specific location for archiving transation log files from PostgreSQL")+ },+ &XLogArchiveDest,+ "", NULL, NULL+ },+ + {+ {"archive_program", PGC_POSTMASTER, WAL_SETTINGS,+ gettext_noop("Archive program"),+ gettext_noop("The external program that will be called to execute the archival process")+ },+ &XLogArchiveProgram,+ "", NULL, NULL+ },+
{
{"client_encoding", PGC_USERSET, CLIENT_CONN_LOCALE,
gettext_noop("Sets the client's character set encoding."),
Index: backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.115
diff -c -r1.115 postgresql.conf.sample
*** backend/utils/misc/postgresql.conf.sample 11 Jul 2004 21:48:25 -0000 1.115
--- backend/utils/misc/postgresql.conf.sample 14 Jul 2004 22:41:52 -0000
***************
*** 113,118 ****
--- 113,129 ----
#---------------------------------------------------------------------------
+ # ARCHIVING+ #---------------------------------------------------------------------------+ + # - Settings -+ + #archive_mode = true # enables archiving of full txn log files+ #archive_dest = '/tmp' # specifies destination of archive files+ #archive_program = 'cp %s %s' # external archiving program command line+ + + #---------------------------------------------------------------------------
# QUERY TUNING
#---------------------------------------------------------------------------
Index: bin/initdb/initdb.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/bin/initdb/initdb.c,v
retrieving revision 1.43
diff -c -r1.43 initdb.c
*** bin/initdb/initdb.c 14 Jul 2004 17:55:10 -0000 1.43
--- bin/initdb/initdb.c 14 Jul 2004 22:41:55 -0000
***************
*** 2023,2029 ****
char *pgdenv; /* PGDATA value got from sent to
* environment */
char *subdirs[] =
! {"global", "pg_xlog", "pg_clog", "pg_subtrans", "base", "base/1", "pg_tblspc"};
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], "initdb");
--- 2023,2030 ----
char *pgdenv; /* PGDATA value got from sent to
* environment */
char *subdirs[] =
!
! {"global", "pg_xlog", "pg_xlog/archive_status", "pg_clog", "pg_subtrans", "base", "base/1", "pg_tblspc"};
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], "initdb");
Index: include/access/xlog.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/access/xlog.h,v
retrieving revision 1.52
diff -c -r1.52 xlog.h
*** include/access/xlog.h 1 Jul 2004 00:51:38 -0000 1.52
--- include/access/xlog.h 14 Jul 2004 22:41:56 -0000
***************
*** 210,215 ****
--- 210,219 ----
extern int XLOGbuffers;
extern char *XLOG_sync_method;
extern const char XLOG_sync_method_default[];
+ extern bool XLogArchiveMode;+ extern bool XLogArchiveDEBUG;+ extern char *XLogArchiveDest;+ extern char *XLogArchiveProgram;
#ifdef WAL_DEBUG
extern bool XLOG_DEBUG;
Index: include/storage/pmsignal.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/storage/pmsignal.h,v
retrieving revision 1.8
diff -c -r1.8 pmsignal.h
*** include/storage/pmsignal.h 29 May 2004 22:48:23 -0000 1.8
--- include/storage/pmsignal.h 14 Jul 2004 22:41:56 -0000
***************
*** 24,29 ****
--- 24,30 ----
{
PMSIGNAL_PASSWORD_CHANGE, /* pg_pwd file has changed */
PMSIGNAL_WAKEN_CHILDREN, /* send a SIGUSR1 signal to all backends */
+ PMSIGNAL_WAKEN_ARCHIVER, /* send a NOTIFY signal to ARCHIVER */
NUM_PMSIGNALS /* Must be last value of enum! */
} PMSignalReason;
recovery.conf.sample (text/plain, 2.3K) download
# -------------------------------
# PostgreSQL recovery config file
# -------------------------------
#
# Edit this file to provide the parameters that PostgreSQL
# needs to perform an archive recovery of a database
#
# This file consists of lines of the form:
#
# name = value
#
# (The ' = ' is NOT optional.)
#
# Comments are introduced with '#' at beginning of a line.
#
# The complete list of option names and
# allowed values can be found in the PostgreSQL documentation. The
# commented-out settings shown in this file show optional values.
#
# These options cannot be specified in any other way. There are
# no command line switches, to ensure that when recovery completes
# we do not accidentally re-enter archive recovery.
#
# This file is read on postmaster startup.
#
#---------------------------------------------------------------------------
# REQUIRED PARAMETERS
#---------------------------------------------------------------------------
#
# restore program
#
# specifies the program that is called to copy log files
# back from archive for use
# command string must specify 3 parameters, using %s
# parameter1 will be substituted with archive_dest
# parameter2 will be substituted with the single log file
# that will be restored (one file per execution)
# parameter3 will be substituted with the full path of
# the file to which the restore program will restore
#
#
restore_program = 'cp %s/%s %s'
#
#
#---------------------------------------------------------------------------
# RECOVERY TARGET
#---------------------------------------------------------------------------
#
# By default, recovery will rollforward to the end of logs
# If you want to stop rollforward before that point, you
# MUST set a recovery target.
#
# You may set a recovery target either by transactionId, or
# by timestamp. Recovery may either include or exclude the
# records with the recovery target value.
#
#
#recovery_target_time = '2004-07-14-22:39:00'
#use_local_time = 'true'
#
recovery_target_xid = '11000'
#
# true or false
recovery_target_inclusive = 'true'
#
#---------------------------------------------------------------------------
# OPTIONAL PARAMETERS
#---------------------------------------------------------------------------
#
recovery_debug_log = 'true'
#
#---------------------------------------------------------------------------
pgarch.c (text/x-c, 13.7K) download
/* ----------
* pgarch.c
*
* PostgreSQL transaction log archiver
*
* All functions relating to archiver are included here
*
* - All functions executed by archiver process
*
* - archiver is forked from postmaster, and the two
* processes then communicate using signals. All functions
* executed by postmaster are included in this file.
*
* Simon Riggs simon@2ndquadrant.com
*
* ----------
*/
#include "postgres.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include "pgarch.h"
#include "storage/fd.h"
#include "miscadmin.h"
#include "access/xlog.h"
#include "libpq/pqsignal.h"
#include "storage/ipc.h"
#include "postmaster/postmaster.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
#include "utils/ps_status.h"
/* ----------
* GUC parameters
* ----------
*/
bool XLogArchiveMode;
bool XLogArchiveDEBUG;
char *XLogArchiveDest;
char *XLogArchiveProgram;
/* ----------
* Other global variables
* ----------
*/
bool pgarch_is_running = false;
/* ----------
* Local data
* ----------
*/
static char XLogDir[MAXPGPATH];
static char XLogArchiveStatusDir[MAXPGPATH];
static time_t last_pgarch_start_time;
static time_t last_pgarch_archivercopy_time;
static time_t curtime;
static bool archiving_in_progress = false;
/* ----------
* Local function forward declarations
* ----------
*/
NON_EXEC_STATIC void pgarch_Main(int argc, char *argv[]);
static void pgarch_exit(SIGNAL_ARGS);
static void pgarch_waken(SIGNAL_ARGS);
static void pgarch_MainWaitLoop(void);
static void pgarch_ArchiverCopyLoop(void);
static bool pgarch_archiveXlog(char *xlog, bool testmode);
static bool pgarch_readyXlog(char *xlog);
static void pgarch_archiveDone(char *xlog);
/* ------------------------------------------------------------
* Public functions called from postmaster follow
* ------------------------------------------------------------
*/
/* ----------
* pgarch_start() -
*
* Called from postmaster at startup
* or after the previous archiver died
*
* Note: if fail, we will be called again from the postmaster main loop.
* ----------
*/
int
pgarch_start(void)
{
time_t curtime;
pid_t pgArchPid;
/*
* Do nothing if no archiver needed
*/
if (!XLogArchiveMode)
return 0;
/*
* Do nothing if too soon since last archiver start. This is a
* safety valve to protect against continuous respawn attempts if the
* archiver is dying immediately at launch. Note that since we will
* be re-called from the postmaster main loop, we will get another
* chance later.
*/
curtime = time(NULL);
if ((unsigned int) (curtime - last_pgarch_start_time) <
(unsigned int) PGARCH_RESTART_INTERVAL) {
return 0;
}
last_pgarch_start_time = curtime;
fflush(stdout);
fflush(stderr);
#ifdef __BEOS__
/* Specific beos actions before backend startup */
beos_before_backend_startup();
#endif
#ifdef EXEC_BACKEND
switch ((pgArchPid = (int) pgarch_forkexec(STAT_PROC_BUFFER)))
#else
switch ((pgArchPid = (int) fork()))
#endif
{
case -1:
#ifdef __BEOS__
/* Specific beos actions */
beos_backend_startup_failed();
#endif
ereport(LOG,
(errmsg("could not fork archiver")));
exit(1);
#ifndef EXEC_BACKEND
case 0:
/* in postmaster child ... */
#ifdef __BEOS__
/* Specific beos actions after backend startup */
beos_backend_startup();
#endif
/* Close the postmaster's sockets */
ClosePostmasterPorts();
/* Drop our connection to postmaster's shared memory, as well */
PGSharedMemoryDetach();
pgarch_Main(0, NULL);
break;
#endif
default:
return (int) pgArchPid;
}
/* shouldn't get here */
return 0;
}
/* ------------------------------------------------------------
* Local functions called by archiver follow
* ------------------------------------------------------------
*/
/* ----------
* pgarch_Main() -
*
*
* The argc/argv parameters are valid only in EXEC_BACKEND case.
* ----------
*/
NON_EXEC_STATIC void
pgarch_Main(int argc, char *argv[])
{
char testxlog[32] = "0000TEST0000TEST";
char testpath[MAXPGPATH];
FILE *rlogFD;
IsUnderPostmaster = true; /* we are a postmaster subprocess now */
MyProcPid = getpid(); /* reset MyProcPid */
/* Lose the postmaster's on-exit routines */
on_exit_reset();
/* Init XLOG file paths */
snprintf(XLogDir, MAXPGPATH, "%s/pg_xlog", DataDir);
snprintf(XLogArchiveStatusDir, MAXPGPATH, "%s/archive_status", XLogDir);
/*
* Test whether archive_program and archive_dest have been set
* correctly in postgresql.conf. This test does *not* look at
* archive_status, since it is a direct test of archival
*/
snprintf(testpath, MAXPGPATH, "%s/%s", XLogDir, testxlog);
unlink(testpath);
rlogFD = AllocateFile(testpath, "w");
if (rlogFD == NULL) {
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write test file to pg_xlog \"%s\": %m",
testpath)));
exit(1);
}
FreeFile(rlogFD);
if (!pgarch_archiveXlog(testxlog, true)) {
ereport(ERROR,
(errmsg("your archive_program failed to archive a test file"),
errhint("your archive_program is not specified correctly "
"or the archive system is currently unavailable. "
"test your program again before restarting")));
exit(1);
}
unlink(testpath);
/*
* Ignore all signals usually bound to some action in the postmaster,
*/
pqsignal(SIGHUP, SIG_IGN);
pqsignal(SIGTERM, SIG_IGN);
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGQUIT, pgarch_exit);
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, pgarch_waken);
pqsignal(SIGUSR2, SIG_IGN);
pqsignal(SIGCHLD, SIG_IGN);
pqsignal(SIGTTIN, SIG_DFL);
pqsignal(SIGTTOU, SIG_DFL);
pqsignal(SIGCONT, SIG_DFL);
pqsignal(SIGWINCH, SIG_DFL);
PG_SETMASK(&UnBlockSig);
#ifdef EXEC_BACKEND
pgstat_parseArgs(argc,argv);
#endif
/*
* Identify via ps
*/
init_ps_display("archiver process", "", "");
set_ps_display("");
ereport(LOG,
(errmsg("archiver started")));
/*
* When first started, check for outstanding archive files
* which may be present if we did a (normal) fast shutdown
* or if archiver died with some form of error
*/
pgarch_ArchiverCopyLoop();
pgarch_MainWaitLoop();
exit(0);
}
static void
pgarch_exit(SIGNAL_ARGS)
{
exit(0);
}
#ifdef NOT_USED
static bool shutdowncalled = false;
/*
* Experiment later with having archiver complete its work on smart shutdown...
*
*/
static void
pgarch_shutdown(SIGNAL_ARGS)
{
shutdowncalled = true;
/*
* we do not block signals here, to allow a later,
* upgraded (fast/immediate) request for shutdown to
* override this behaviour
*/
if (archiving_in_progress) {
/* complete archiving, then exit
*/
return;
}
else {
/* This is a Smart or Fast Shutdown,
* so try archiving one last time. This is consistent
* with behaviour of a fast shutdown, in that we still
* write a shutdown checkpoint and try to recycle the
* log files - so before we do this, we try to archive
* away the last few .ready xlogs
*
* XXX archive partial xlog file, even though not full
* though we'd have to remember which one was next to
* handle that correctly on restore/recover
*/
pgarch_ArchiverCopyLoop();
return;
}
}
#endif
static void
pgarch_waken(SIGNAL_ARGS)
{
pgarch_ArchiverCopyLoop();
return;
}
/* ----------
* pgarch_MainWaitLoop() -
*
* Main wait loop for archiver
* ----------
*/
static void
pgarch_MainWaitLoop(void)
{
/*
* There shouldn't be anything for the archiver to do except
* to wait, so we could use pause(3) here...
* ...however, the archiver exists to protect our data, so
* she wakes up occaisionally to allow herself to be proactive.
* This shouldn't be required, but our data is important
* and this won't hurt to be cautious
*/
do {
/*
* Sleep for a while, hoping to be interrupted by signal
* if no signal, then check anyway....just to be sure
*/
sleep(PGARCH_AUTOWAKE_INTERVAL);
curtime = time(NULL);
if ((unsigned int) (curtime - last_pgarch_archivercopy_time) >=
(unsigned int) PGARCH_AUTOWAKE_INTERVAL) {
pgarch_ArchiverCopyLoop();
}
last_pgarch_archivercopy_time = curtime;
} while (PostmasterIsAlive(true));
return;
}
/* ----------
* pgarch_ArchiverCopyLoop() -
*
* Archives all outstanding xlogs then exits
* ----------
*/
static void
pgarch_ArchiverCopyLoop(void)
{
char xlog[32];
int try = 1;
/*
* We continue to trap for all signals, except for the
* one that brought us here in the first place. We
* loop through all transaction log files that require
* archiving, so no need to be interrupted to continue
* that task
*/
pqsignal(SIGUSR1, SIG_IGN);
archiving_in_progress = true;
/*
* loop through all xlogs with archive_status of .ready
* then archive them...mostly we expect this to be a single
* file, though not just a simple loop because we may add new
* files onto the list of those that need archiving while we
* are still copying earlier archives
*/
while (pgarch_readyXlog(xlog) && try <= NUM_ARCHIVE_RETRIES)
{
if (pgarch_archiveXlog(xlog, false)) {
pgarch_archiveDone(xlog);
} else {
ereport(WARNING,
(errmsg("transaction log file \"%s\" could not be archived",
xlog),
errhint("archive_dest \"%s\" may not have enough disk space "
"or the archive system is currently unavailable",
XLogArchiveDest)));
}
/* if we have copied one file, we do not wait:
immediately loop back round and check to see if another is there.
Hopefully, we're quick enough....so we fall out and sleep again
*/
try++;
}
archiving_in_progress = false;
pqsignal(SIGUSR1, pgarch_waken);
return;
}
/*
* pgarch_archiveXlog
*
* Invokes system(3) to copy one archive file to XLogArchiveDest
* We assume xlog is a correct filename and that both
* XLogArchiveProgram and XLogArchiveDest are set correctly
*/
static bool
pgarch_archiveXlog(char *xlog, bool testmode)
{
char xlogarchcmd[MAXPGPATH];
char xlogpath[MAXPGPATH];
int rc;
snprintf(xlogpath, MAXPGPATH, "%s/%s", XLogDir,xlog);
/*
* set the string for the program and its parameters
* XLogArchiveProgram should contain 2 positional parameters
* xlog must be a full path to xlog
*/
snprintf(xlogarchcmd, MAXPGPATH, XLogArchiveProgram, xlogpath, XLogArchiveDest);
if (XLogArchiveDEBUG)
ereport(LOG,
(errmsg("archive command=\"%s\"",
xlogarchcmd)));
rc = system(xlogarchcmd);
if (rc != 0 ) {
ereport(LOG,
(errmsg("archive command=\"%s\",return code=%i",
xlogarchcmd, rc)));
return false;
}
if (!testmode)
ereport(LOG,
(errmsg("archived transaction log file \"%s\"", xlog)));
return true;
}
/*
* XLogArchiveXlogs
*
* Return name of the oldest xlog file that has not yet been archived.
* No notification is set that file archiving is now in progress, [so
* this would need to be extended if multiple concurrent archival
* tasks were created]. If a failure occurs, we would completely
* re-copy the file at the next available opportunity.
*
* It is important that we return the oldest, so that we archive xlogs
* in order that they were written, for two reasons:
* 1) to maintain the sequential chain of xlogs required for recovery
* 2) because the oldest ones will sooner become candidates for
* recycling at time of checkpoint
*
*/
static bool
pgarch_readyXlog(char *xlog)
{
/*
* open XLogArchive directory and read through list of
* rlogs that have the .ready suffix, looking for earliest file.
* It is possible to optimise this code, though only a single
* file is expected on the vast majority of calls, so....
*/
char newxlog[32];
char emptystr[32] = "\0";
DIR *rldir;
struct dirent *rlde;
bool firstfile;
rldir = AllocateDir(XLogArchiveStatusDir);
if (rldir == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open archive_status directory \"%s\": %m",
XLogArchiveStatusDir)));
firstfile = true;
while ((rlde = readdir(rldir)) != NULL)
{
if (strlen(rlde->d_name) == 22 &&
strspn(rlde->d_name, "0123456789ABCDEF") == 16 &&
strcmp(rlde->d_name + 16, ".ready") == 0)
{
if (firstfile) {
strcpy(newxlog, rlde->d_name);
firstfile = false;
} else {
if (strcmp(rlde->d_name, newxlog) <= 0)
strcpy(newxlog, rlde->d_name);
}
}
}
FreeDir(rldir);
if (!firstfile) {
strcpy(xlog, emptystr);
strncat(xlog, newxlog, 16);
return true;
}
else
return false;
}
/*
* pgarch_archiveDone
*
* Write notification that an xlog has now been successfully archived
*/
static void
pgarch_archiveDone(char *xlog)
{
char rlogready[MAXPGPATH];
char rlogdone[MAXPGPATH];
int rc;
snprintf(rlogready, MAXPGPATH, "%s/%s.ready", XLogArchiveStatusDir, xlog);
snprintf(rlogdone, MAXPGPATH, "%s/%s.done", XLogArchiveStatusDir, xlog);
rc = rename(rlogready, rlogdone);
if (rc < 0) {
ereport(WARNING,(errcode_for_file_access(),
errmsg("could not update archive_status for \"%s\"",
rlogready)));
return;
}
return;
}
pgarch.h (text/x-c-header, 480B) download
/* ----------
* pgarch.h
*
* Definitions for the PostgreSQL archiver daemon.
*
* ----------
*/
/* ----------
* Timer definitions.
* ----------
*/
#define PGARCH_AUTOWAKE_INTERVAL 600 /* How often to wake and poll */
#define PGARCH_RESTART_INTERVAL 60 /* How often to attempt to restart */
/* a failed statistics collector; in seconds. */
#define NUM_ARCHIVE_RETRIES 3
/* ----------
* Functions called from postmaster
* ----------
*/
extern int pgarch_start(void);
README (text/html, 11.6K) download
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 TRANSITIONAL//EN">
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=UTF-8">
<META NAME="GENERATOR" CONTENT="GtkHTML/3.0.9">
</HEAD>
<BODY>
<TT>PITR Archive Recovery, 8 July 2004<BR>
<BR>
What's in this patch?<BR>
<BR>
- All code from previous patches: working now against CVS tip as of now<BR>
- Partially complete code for PITR, though not enabled as yet.<BR>
<BR>
This is a full, working patch for discussion and testing, while I complete the additional code.<BR>
<BR>
Archive Recovery Overview<BR>
<BR>
The rule is: when you startup with archive_mode on, take a backup.<BR>
<BR>
This patch provides an additional mode for transactional log recovery.<BR>
The current mode provides Crash Recovery, allowing the database to<BR>
restart and to read the last few online transaction logs to recover. The<BR>
additional mode is known as Archive Recovery, enabled in postgresql.conf<BR>
using archive_mode=true.<BR>
<BR>
The administrator also specifies and archive location and a program to<BR>
perform the copying (discussed later). As database writes occur and<BR>
transaction logs fill they are copied to the archive location by a new<BR>
sub-process of the postmaster, called the archiver process.<BR>
<BR>
If the database crashes when in archive_mode, it will recover just as<BR>
before. If a major system crash, or other reason to restore occurs, then<BR>
the new Archive Recovery mode can be invoked by:<BR>
- restoring the full physical backup<BR>
- creating DataDir/recovery.conf<BR>
- restart PostgreSQL<BR>
<BR>
PostgreSQL will recover using almost the same code path as before,<BR>
except: all transaction logs will be restored for use directly from<BR>
archive in a stream that prevents disk space from overflowing. Extended<BR>
recovery is possible, over many transaction logs - far more than was<BR>
previously possible.<BR>
<BR>
The essential aspect to recovery is maintaining a solid backup/log<BR>
chain. This must consist of:<BR>
i) a full backup of every file in PostgreSQL - miss none!<BR>
ii) archived transaction logs from just before backup taken to whenever<BR>
you want to recover to...<BR>
<BR>
If you "break the chain" i.e. the two sets of files don't match, or<BR>
you've deleted a transaction log or lost a tape - then you will only be<BR>
able to recover up to the point the chain broke. The longer the chain,<BR>
the easier it will be to break, so backup regularly and work out a<BR>
robust archiving policy.<BR>
<BR>
Extensibility<BR>
Archive Recovery allows the administrator to specify a program or script<BR>
to use when both archiving and restoring transaction log files. As a<BR>
result, the administrator can choose to store transaction log files on<BR>
any drive, any other system or integrating with any Backup Archive<BR>
Recovery (BAR) software.<BR>
<BR>
The Archive Program is provided with 3 parameters, which can be placed<BR>
anywhere within the supplied command string. Each parameter is<BR>
represented by a "%s" character string.<BR>
- The first parameter is replaced with the full path of the transaction<BR>
log to be archived.<BR>
- The second parameter is replaced with the value of the archive_dest<BR>
parameter<BR>
e.g. with these settings<BR>
archive_mode = true<BR>
archive_dest = '/mount/disk2/pgarchive'<BR>
archive_program = 'cp %s %s'<BR>
with transaction log 00000000000003A4 to archive<BR>
with DataDir of web123<BR>
<BR>
would execute the following command <BR>
'cp /usr/local/pgsql/web123/00000000000003A4 /disk2/pgarchive'<BR>
<BR>
To enter Archive Recovery the administrator must create a recovery.conf<BR>
file. [Currently an empty file will do, though intended as the one place<BR>
where all recovery options and parameters would be set]<BR>
<BR>
The Restore Program is provided with 2 parameters<BR>
- The first parameter is replaced with the value of the archive_dest<BR>
parameter<BR>
- The second parameter is replaced with the value of the transaction log<BR>
which recovery is requesting should now be restored<BR>
- The third parameter is replaced with the full path of the filename<BR>
which should be the target of the single file recovery operation.<BR>
[In this patch, XlogArchRestoreProgram is hardcoded to "cp %s/%s %s",<BR>
pending short discussion on how to specify this...]<BR>
<BR>
Possibilities<BR>
Using the current features, it is possible to implement an Automated<BR>
Standby Database. This is an active-passive High Availability option. In<BR>
this mode, the main server sends archived log files to a second, standby<BR>
server. The standby server is set up to be in "permanent recovery", by<BR>
using a RestoreProgram that waits for each file to be shipped to it. The<BR>
standby system receives each file, then recovers up to that point - so<BR>
the standby system is always a few seconds from completing its startup<BR>
should it be required.<BR>
<BR>
Patch Status & Current Caveats<BR>
- A number of recovery scenarios have been tested and the patch is<BR>
believed to be stable and ready for others to begin commentary and<BR>
testing...please understand that there are many scenarios that work and<BR>
many that do not...these last are not bugs<BR>
<BR>
- Recovery to a specific point in time is not yet implemented. The<BR>
administrator has the following recovery options:<BR>
--can recover the system fully, all the way to the end of logs<BR>
--stop recovery by withholding log files at an appropriate place,<BR>
thereby forcing the termination of recovery<BR>
<BR>
How it Works<BR>
<BR>
archive_debug is a hidden postgresql.conf setting which can be used to<BR>
show more debug output for the archive recovery facilities.<BR>
...archive_debug = true<BR>
<BR>
[This currently generates an additional log file called recovery.log,<BR>
showing the record headers and types of commit/abort xlog records.]<BR>
<BR>
Each PostgreSQL user has a corresponding backend process performing work<BR>
for them. These backends write any transaction log data to the log as<BR>
each operation occurs, then marks transactions as either committed or<BR>
aborted. The log is split into log segment files. When each log file<BR>
fills a notification file is created in archive_status, and at the same<BR>
time a signal is sent to the archiver (via the postmaster) to begin<BR>
archiving.<BR>
<BR>
The archiver executes the administrator's program or script using a<BR>
system(3) call, archiving each file one at a time.<BR>
<BR>
The notification file provides a number of features. First, its<BR>
simplicity ensures that we don't need a shared memory link between each<BR>
backend and the archiver, which would then be prone to failure - which<BR>
is exactly not what we want. Second, the archive_status file is written<BR>
whether or not the postmaster is still up, so will still function<BR>
correctly even when the worst has happened. Third, as a persistent<BR>
record, it will allow archiving to restart at the same point later on<BR>
(even after a backup/restore), allowing it to recover gracefully from<BR>
administrator's immediate shutdown requests as well as postmaster or<BR>
archive system failures.<BR>
<BR>
When recovery completes, archive_status files are written to ensure<BR>
cleanup of all transaction logs leaves the database in a fit state for<BR>
production. The recovery.conf file is removed to ensure that a<BR>
subsequent crash doesn't accidentally begin recovery from archives...<BR>
<BR>
The archiver process starts only when recovery has fully completed.<BR>
<BR>
How to Fail<BR>
<BR>
All of the following ways to fail have already occurred in testing...<BR>
<BR>
1. if you do all 3 of these, you will not have a backup/log chain:<BR>
a) use the 'cp' command, or any other command that can write to either a<BR>
file or a directory<BR>
b) use archive_dest to specify a directory<BR>
c) forget to create the directory, which the command in a) then<BR>
interprets as a file and then all archived logs overwrite each other and<BR>
break the chain. You will be unable to rollforward AT ALL.<BR>
<BR>
2. if you do all 5 of these, you will not rollforward very far, if any.<BR>
a) restore the database backup<BR>
b) forget to specify a recovery.conf file<BR>
c) startup and let database recover using only the xlogs that were<BR>
present when the backup was taken<BR>
d) then immediately after recovery run a job which causes xlog to be<BR>
written, and then a file to fill<BR>
e) use an archive command that allows overwriting of previously archived<BR>
files<BR>
This sequence causes the database to recover successfully, but not using<BR>
your full archive chain. The current redo pointer points half way<BR>
through your backup/log chain, so when the file is archived, it<BR>
overwrites a file in the middle of the chain, thus breaking the chain.<BR>
There isn't an option on cp that will prevent this...<BR>
<BR>
3. turn archive_mode on and off...<BR>
Turning archive_mode off means that when an xlog fills, it won't EVER<BR>
get archived. This will break the backup/log chain. If you turn<BR>
archive_mode back on, and an xlog filled, then it will not be picked up<BR>
as having filled and you will be missing a link in the chain. Sometimes<BR>
you'll get away with it and then you'll think it will work all the time.<BR>
The rule is: when you startup with archive_mode on, take a backup.<BR>
<BR>
4. Forget that xlogs are the same size, whether they are full or<BR>
not...you can't tell (yet) by looking at one whether it has a record<BR>
within it that will break the chain<BR>
<BR>
How to succeed<BR>
- Test your recovery procedures before you need them<BR>
- When you're in a recovery situation backup everything you can lay your<BR>
hands on, to make sure you have a known position to return to while you<BR>
ATTEMPT recovery - you may make a mistake and need to retry<BR>
- be cool - we all make mistakes, just don't let errors multiply<BR>
<BR>
- disable listen_addresses in postgresql.conf when you recover, to give<BR>
yourself some breathing space to check things, when you finally do get<BR>
the database ready<BR>
- recover with archive_debug = true to give me/others a chance to debug<BR>
any problems or answer any questions you may have<BR>
<BR>
The rule is: when you startup with archive_mode on, take a backup. <BR>
<BR>
When enabling archive_mode for the first time, if you have run<BR>
previously without enabling archive_mode, then when you will inevitably<BR>
get messages saying "cannot find archive_status file". After you have<BR>
taken a backup, you can manually create files in the archive_status<BR>
directory of <xlog>.done, which will then allow the bgwriter to clean<BR>
them up when it next takes a checkpoint.<BR>
<BR>
Additional Work<BR>
<BR>
- recovery.conf needs some minor work to specify recover options. It is<BR>
envisaged that this would be a short bison grammar, very very similar to<BR>
postgresql.conf<BR>
<BR>
- It seems possible to easily work around the requirement to take the<BR>
backup while the database is open. It would be a good option to have<BR>
both hot and cold backup options...<BR>
<BR>
- full documentation will also follow shortly<BR>
<BR>
Credits<BR>
<BR>
This work is the final push in a long series of patches and discussions<BR>
about how to achieve archive recovery with PostgreSQL. The work of<BR>
J.R.Nield and Patrick MacDonald has provided the detailed underpinnings<BR>
for this recent work, which in turn rests upon Vadim Mikheev's original<BR>
work on WAL and MVCC. Tom Lane has provided considerable technical<BR>
assistance and quality review, whilst Bruce Momjian has provided many of<BR>
the ideas and smoothed the way for much of the work. Thanks, all.<BR>
<BR>
Simon Riggs, <A HREF="mailto:simon@2ndquadrant.com"><U>simon@2ndquadrant.com</TT></U></A><BR>
<BR>
</BODY>
</HTML>
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-admin@postgresql.org
Cc: simon@2ndquadrant.com, markw@osdl.org, pgman@candle.pha.pa.us, kn@mgnet.de, tgl@sss.pgh.pa.us, pgsql-patches@postgresql.org
Subject: Re: [HACKERS] Point in Time Recovery
In-Reply-To: <1089845797.17493.4987.camel@stromboli>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox