pg.ddx.io  pgsql-admin@postgresql.org mailing list archive  
help / color / mirror / Atom feed
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 &quot;break the chain&quot; 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 &quot;%s&quot; 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 &quot;cp %s/%s %s&quot;,<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 &quot;permanent recovery&quot;, 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 &amp; 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 &quot;cannot find archive_status file&quot;. After you have<BR> taken a backup, you can manually create files in the archive_status<BR> directory of &lt;xlog&gt;.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>

view thread (111+ messages)  latest in thread

Message-ID: <1089845797.17493.4987.camel@stromboli>
Permalink:  ../1089845797.17493.4987.camel@stromboli/
Also on:    postgresql.org/message-id/1089845797.17493.4987.camel@stromboli

 ·  · 

reply

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