Commit b0daba57 authored by Heikki Linnakangas's avatar Heikki Linnakangas

Tolerate timeline switches while "pg_basebackup -X fetch" is running.

If you take a base backup from a standby server with "pg_basebackup -X
fetch", and the timeline switches while the backup is being taken, the
backup used to fail with an error "requested WAL segment %s has already
been removed". This is because the server-side code that sends over the
required WAL files would not construct the WAL filename with the correct
timeline after a switch.

Fix that by using readdir() to scan pg_xlog for all the WAL segments in the
range, regardless of timeline.

Also, include all timeline history files in the backup, if taken with
"-X fetch". That fixes another related bug: If a timeline switch happened
just before the backup was initiated in a standby, the WAL segment
containing the initial checkpoint record contains WAL from the older
timeline too. Recovery will not accept that without a timeline history file
that lists the older timeline.

Backpatch to 9.2. Versions prior to that were not affected as you could not
take a base backup from a standby before 9.2.
parent ee994272
...@@ -2797,18 +2797,33 @@ PreallocXlogFiles(XLogRecPtr endptr) ...@@ -2797,18 +2797,33 @@ PreallocXlogFiles(XLogRecPtr endptr)
} }
/* /*
* Get the segno of the latest removed or recycled WAL segment. * Throws an error if the given log segment has already been removed or
* Returns 0/0 if no WAL segments have been removed since startup. * recycled. The caller should only pass a segment that it knows to have
* existed while the server has been running, as this function always
* succeeds if no WAL segments have been removed since startup.
* 'tli' is only used in the error message.
*/ */
void void
XLogGetLastRemoved(XLogSegNo *segno) CheckXLogRemoved(XLogSegNo segno, TimeLineID tli)
{ {
/* use volatile pointer to prevent code rearrangement */ /* use volatile pointer to prevent code rearrangement */
volatile XLogCtlData *xlogctl = XLogCtl; volatile XLogCtlData *xlogctl = XLogCtl;
XLogSegNo lastRemovedSegNo;
SpinLockAcquire(&xlogctl->info_lck); SpinLockAcquire(&xlogctl->info_lck);
*segno = xlogctl->lastRemovedSegNo; lastRemovedSegNo = xlogctl->lastRemovedSegNo;
SpinLockRelease(&xlogctl->info_lck); SpinLockRelease(&xlogctl->info_lck);
if (segno <= lastRemovedSegNo)
{
char filename[MAXFNAMELEN];
XLogFileName(filename, tli, segno);
ereport(ERROR,
(errcode_for_file_access(),
errmsg("requested WAL segment %s has already been removed",
filename)));
}
} }
/* /*
......
...@@ -56,14 +56,13 @@ static void base_backup_cleanup(int code, Datum arg); ...@@ -56,14 +56,13 @@ static void base_backup_cleanup(int code, Datum arg);
static void perform_base_backup(basebackup_options *opt, DIR *tblspcdir); static void perform_base_backup(basebackup_options *opt, DIR *tblspcdir);
static void parse_basebackup_options(List *options, basebackup_options *opt); static void parse_basebackup_options(List *options, basebackup_options *opt);
static void SendXlogRecPtrResult(XLogRecPtr ptr); static void SendXlogRecPtrResult(XLogRecPtr ptr);
static int compareWalFileNames(const void *a, const void *b);
/* Was the backup currently in-progress initiated in recovery mode? */ /* Was the backup currently in-progress initiated in recovery mode? */
static bool backup_started_in_recovery = false; static bool backup_started_in_recovery = false;
/* /*
* Size of each block sent into the tar stream for larger files. * Size of each block sent into the tar stream for larger files.
*
* XLogSegSize *MUST* be evenly dividable by this
*/ */
#define TAR_SEND_SIZE 32768 #define TAR_SEND_SIZE 32768
...@@ -227,64 +226,201 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir) ...@@ -227,64 +226,201 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
* We've left the last tar file "open", so we can now append the * We've left the last tar file "open", so we can now append the
* required WAL files to it. * required WAL files to it.
*/ */
XLogSegNo logsegno; char pathbuf[MAXPGPATH];
XLogSegNo endlogsegno; XLogSegNo segno;
XLogSegNo startsegno;
XLogSegNo endsegno;
struct stat statbuf; struct stat statbuf;
List *historyFileList = NIL;
List *walFileList = NIL;
char **walFiles;
int nWalFiles;
char firstoff[MAXFNAMELEN];
char lastoff[MAXFNAMELEN];
DIR *dir;
struct dirent *de;
int i;
ListCell *lc;
TimeLineID tli;
MemSet(&statbuf, 0, sizeof(statbuf)); /*
statbuf.st_mode = S_IRUSR | S_IWUSR; * I'd rather not worry about timelines here, so scan pg_xlog and
#ifndef WIN32 * include all WAL files in the range between 'startptr' and 'endptr',
statbuf.st_uid = geteuid(); * regardless of the timeline the file is stamped with. If there are
statbuf.st_gid = getegid(); * some spurious WAL files belonging to timelines that don't belong
#endif * in this server's history, they will be included too. Normally there
statbuf.st_size = XLogSegSize; * shouldn't be such files, but if there are, there's little harm in
statbuf.st_mtime = time(NULL); * including them.
*/
XLByteToSeg(startptr, startsegno);
XLogFileName(firstoff, ThisTimeLineID, startsegno);
XLByteToPrevSeg(endptr, endsegno);
XLogFileName(lastoff, ThisTimeLineID, endsegno);
XLByteToSeg(startptr, logsegno); dir = AllocateDir("pg_xlog");
XLByteToPrevSeg(endptr, endlogsegno); if (!dir)
ereport(ERROR,
(errmsg("could not open directory \"%s\": %m", "pg_xlog")));
while ((de = ReadDir(dir, "pg_xlog")) != NULL)
{
/* Does it look like a WAL segment, and is it in the range? */
if (strlen(de->d_name) == 24 &&
strspn(de->d_name, "0123456789ABCDEF") == 24 &&
strcmp(de->d_name + 8, firstoff + 8) >= 0 &&
strcmp(de->d_name + 8, lastoff + 8) <= 0)
{
walFileList = lappend(walFileList, pstrdup(de->d_name));
}
/* Does it look like a timeline history file? */
else if (strlen(de->d_name) == 8 + strlen(".history") &&
strspn(de->d_name, "0123456789ABCDEF") == 8 &&
strcmp(de->d_name + 8, ".history") == 0)
{
historyFileList = lappend(historyFileList, pstrdup(de->d_name));
}
}
FreeDir(dir);
while (true) /*
* Before we go any further, check that none of the WAL segments we
* need were removed.
*/
CheckXLogRemoved(startsegno, ThisTimeLineID);
/*
* Put the WAL filenames into an array, and sort. We send the files
* in order from oldest to newest, to reduce the chance that a file
* is recycled before we get a chance to send it over.
*/
nWalFiles = list_length(walFileList);
walFiles = palloc(nWalFiles * sizeof(char *));
i = 0;
foreach(lc, walFileList)
{ {
/* Send another xlog segment */ walFiles[i++] = lfirst(lc);
char fn[MAXPGPATH]; }
int i; qsort(walFiles, nWalFiles, sizeof(char *), compareWalFileNames);
/*
* Sanity check: the first and last segment should cover startptr and
* endptr, with no gaps in between.
*/
XLogFromFileName(walFiles[0], &tli, &segno);
if (segno != startsegno)
{
char startfname[MAXFNAMELEN];
XLogFileName(startfname, ThisTimeLineID, startsegno);
ereport(ERROR,
(errmsg("could not find WAL file %s", startfname)));
}
for (i = 0; i < nWalFiles; i++)
{
XLogSegNo currsegno = segno;
XLogSegNo nextsegno = segno + 1;
XLogFilePath(fn, ThisTimeLineID, logsegno); XLogFromFileName(walFiles[i], &tli, &segno);
_tarWriteHeader(fn, NULL, &statbuf); if (!(nextsegno == segno || currsegno == segno))
{
char nextfname[MAXFNAMELEN];
XLogFileName(nextfname, ThisTimeLineID, nextsegno);
ereport(ERROR,
(errmsg("could not find WAL file %s", nextfname)));
}
}
if (segno != endsegno)
{
char endfname[MAXFNAMELEN];
XLogFileName(endfname, ThisTimeLineID, endsegno);
ereport(ERROR,
(errmsg("could not find WAL file %s", endfname)));
}
/* Send the actual WAL file contents, block-by-block */ /* Ok, we have everything we need. Send the WAL files. */
for (i = 0; i < XLogSegSize / TAR_SEND_SIZE; i++) for (i = 0; i < nWalFiles; i++)
{ {
FILE *fp;
char buf[TAR_SEND_SIZE]; char buf[TAR_SEND_SIZE];
XLogRecPtr ptr; size_t cnt;
pgoff_t len = 0;
XLogSegNoOffsetToRecPtr(logsegno, TAR_SEND_SIZE * i, ptr); snprintf(pathbuf, MAXPGPATH, XLOGDIR "/%s", walFiles[i]);
XLogFromFileName(walFiles[i], &tli, &segno);
fp = AllocateFile(pathbuf, "rb");
if (fp == NULL)
{
/* /*
* Some old compilers, e.g. gcc 2.95.3/x86, think that passing * Most likely reason for this is that the file was already
* a struct in the same function as a longjump might clobber a * removed by a checkpoint, so check for that to get a better
* variable. bjm 2011-02-04 * error message.
* http://lists.apple.com/archives/xcode-users/2003/Dec//msg000
* 51.html
*/ */
XLogRead(buf, ThisTimeLineID, ptr, TAR_SEND_SIZE); CheckXLogRemoved(segno, tli);
if (pq_putmessage('d', buf, TAR_SEND_SIZE))
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not open file \"%s\": %m", pathbuf)));
}
if (fstat(fileno(fp), &statbuf) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m",
pathbuf)));
if (statbuf.st_size != XLogSegSize)
{
CheckXLogRemoved(segno, tli);
ereport(ERROR,
(errcode_for_file_access(),
errmsg("unexpected WAL file size \"%s\"", walFiles[i])));
}
_tarWriteHeader(pathbuf, NULL, &statbuf);
while ((cnt = fread(buf, 1, Min(sizeof(buf), XLogSegSize - len), fp)) > 0)
{
CheckXLogRemoved(segno, tli);
/* Send the chunk as a CopyData message */
if (pq_putmessage('d', buf, cnt))
ereport(ERROR, ereport(ERROR,
(errmsg("base backup could not send data, aborting backup"))); (errmsg("base backup could not send data, aborting backup")));
len += cnt;
if (len == XLogSegSize)
break;
}
if (len != XLogSegSize)
{
CheckXLogRemoved(segno, tli);
ereport(ERROR,
(errcode_for_file_access(),
errmsg("unexpected WAL file size \"%s\"", walFiles[i])));
}
/* XLogSegSize is a multiple of 512, so no need for padding */
FreeFile(fp);
} }
/* /*
* Files are always fixed size, and always end on a 512 byte * Send timeline history files too. Only the latest timeline history
* boundary, so padding is never necessary. * file is required for recovery, and even that only if there happens
* to be a timeline switch in the first WAL segment that contains the
* checkpoint record, or if we're taking a base backup from a standby
* server and the target timeline changes while the backup is taken.
* But they are small and highly useful for debugging purposes, so
* better include them all, always.
*/ */
foreach(lc, historyFileList)
{
char *fname = lfirst(lc);
snprintf(pathbuf, MAXPGPATH, XLOGDIR "/%s", fname);
if (lstat(pathbuf, &statbuf) != 0)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat file \"%s\": %m", pathbuf)));
/* Advance to the next WAL file */ sendFile(pathbuf, pathbuf, &statbuf, false);
logsegno++;
/* Have we reached our stop position yet? */
if (logsegno > endlogsegno)
break;
} }
/* Send CopyDone message for the last tar file */ /* Send CopyDone message for the last tar file */
...@@ -293,6 +429,19 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir) ...@@ -293,6 +429,19 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
SendXlogRecPtrResult(endptr); SendXlogRecPtrResult(endptr);
} }
/*
* qsort comparison function, to compare log/seg portion of WAL segment
* filenames, ignoring the timeline portion.
*/
static int
compareWalFileNames(const void *a, const void *b)
{
char *fna = *((char **) a);
char *fnb = *((char **) b);
return strcmp(fna + 8, fnb + 8);
}
/* /*
* Parse the base backup options passed down by the parser * Parse the base backup options passed down by the parser
*/ */
......
...@@ -1170,7 +1170,6 @@ XLogRead(char *buf, TimeLineID tli, XLogRecPtr startptr, Size count) ...@@ -1170,7 +1170,6 @@ XLogRead(char *buf, TimeLineID tli, XLogRecPtr startptr, Size count)
char *p; char *p;
XLogRecPtr recptr; XLogRecPtr recptr;
Size nbytes; Size nbytes;
XLogSegNo lastRemovedSegNo;
XLogSegNo segno; XLogSegNo segno;
retry: retry:
...@@ -1263,13 +1262,8 @@ retry: ...@@ -1263,13 +1262,8 @@ retry:
* read() succeeds in that case, but the data we tried to read might * read() succeeds in that case, but the data we tried to read might
* already have been overwritten with new WAL records. * already have been overwritten with new WAL records.
*/ */
XLogGetLastRemoved(&lastRemovedSegNo);
XLByteToSeg(startptr, segno); XLByteToSeg(startptr, segno);
if (segno <= lastRemovedSegNo) CheckXLogRemoved(segno, ThisTimeLineID);
ereport(ERROR,
(errcode_for_file_access(),
errmsg("requested WAL segment %s has already been removed",
XLogFileNameP(sendTimeLine, segno))));
/* /*
* During recovery, the currently-open WAL file might be replaced with the * During recovery, the currently-open WAL file might be replaced with the
......
...@@ -267,7 +267,7 @@ extern bool XLogNeedsFlush(XLogRecPtr RecPtr); ...@@ -267,7 +267,7 @@ extern bool XLogNeedsFlush(XLogRecPtr RecPtr);
extern int XLogFileInit(XLogSegNo segno, bool *use_existent, bool use_lock); extern int XLogFileInit(XLogSegNo segno, bool *use_existent, bool use_lock);
extern int XLogFileOpen(XLogSegNo segno); extern int XLogFileOpen(XLogSegNo segno);
extern void XLogGetLastRemoved(XLogSegNo *segno); extern void CheckXLogRemoved(XLogSegNo segno, TimeLineID tli);
extern void XLogSetAsyncXactLSN(XLogRecPtr record); extern void XLogSetAsyncXactLSN(XLogRecPtr record);
extern Buffer RestoreBackupBlock(XLogRecPtr lsn, XLogRecord *record, extern Buffer RestoreBackupBlock(XLogRecPtr lsn, XLogRecord *record,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment