mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
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.
This commit is contained in:
parent
ee994272ca
commit
b0daba57bb
@ -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);
|
|||||||
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)
|
|||||||
* 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;
|
||||||
MemSet(&statbuf, 0, sizeof(statbuf));
|
List *walFileList = NIL;
|
||||||
statbuf.st_mode = S_IRUSR | S_IWUSR;
|
char **walFiles;
|
||||||
#ifndef WIN32
|
int nWalFiles;
|
||||||
statbuf.st_uid = geteuid();
|
char firstoff[MAXFNAMELEN];
|
||||||
statbuf.st_gid = getegid();
|
char lastoff[MAXFNAMELEN];
|
||||||
#endif
|
DIR *dir;
|
||||||
statbuf.st_size = XLogSegSize;
|
struct dirent *de;
|
||||||
statbuf.st_mtime = time(NULL);
|
|
||||||
|
|
||||||
XLByteToSeg(startptr, logsegno);
|
|
||||||
XLByteToPrevSeg(endptr, endlogsegno);
|
|
||||||
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
/* Send another xlog segment */
|
|
||||||
char fn[MAXPGPATH];
|
|
||||||
int i;
|
int i;
|
||||||
|
ListCell *lc;
|
||||||
XLogFilePath(fn, ThisTimeLineID, logsegno);
|
TimeLineID tli;
|
||||||
_tarWriteHeader(fn, NULL, &statbuf);
|
|
||||||
|
|
||||||
/* Send the actual WAL file contents, block-by-block */
|
|
||||||
for (i = 0; i < XLogSegSize / TAR_SEND_SIZE; i++)
|
|
||||||
{
|
|
||||||
char buf[TAR_SEND_SIZE];
|
|
||||||
XLogRecPtr ptr;
|
|
||||||
|
|
||||||
XLogSegNoOffsetToRecPtr(logsegno, TAR_SEND_SIZE * i, ptr);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some old compilers, e.g. gcc 2.95.3/x86, think that passing
|
* I'd rather not worry about timelines here, so scan pg_xlog and
|
||||||
* a struct in the same function as a longjump might clobber a
|
* include all WAL files in the range between 'startptr' and 'endptr',
|
||||||
* variable. bjm 2011-02-04
|
* regardless of the timeline the file is stamped with. If there are
|
||||||
* http://lists.apple.com/archives/xcode-users/2003/Dec//msg000
|
* some spurious WAL files belonging to timelines that don't belong
|
||||||
* 51.html
|
* in this server's history, they will be included too. Normally there
|
||||||
|
* shouldn't be such files, but if there are, there's little harm in
|
||||||
|
* including them.
|
||||||
*/
|
*/
|
||||||
XLogRead(buf, ThisTimeLineID, ptr, TAR_SEND_SIZE);
|
XLByteToSeg(startptr, startsegno);
|
||||||
if (pq_putmessage('d', buf, TAR_SEND_SIZE))
|
XLogFileName(firstoff, ThisTimeLineID, startsegno);
|
||||||
|
XLByteToPrevSeg(endptr, endsegno);
|
||||||
|
XLogFileName(lastoff, ThisTimeLineID, endsegno);
|
||||||
|
|
||||||
|
dir = AllocateDir("pg_xlog");
|
||||||
|
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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
walFiles[i++] = lfirst(lc);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
XLogFromFileName(walFiles[i], &tli, &segno);
|
||||||
|
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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ok, we have everything we need. Send the WAL files. */
|
||||||
|
for (i = 0; i < nWalFiles; i++)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char buf[TAR_SEND_SIZE];
|
||||||
|
size_t cnt;
|
||||||
|
pgoff_t len = 0;
|
||||||
|
|
||||||
|
snprintf(pathbuf, MAXPGPATH, XLOGDIR "/%s", walFiles[i]);
|
||||||
|
XLogFromFileName(walFiles[i], &tli, &segno);
|
||||||
|
|
||||||
|
fp = AllocateFile(pathbuf, "rb");
|
||||||
|
if (fp == NULL)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Most likely reason for this is that the file was already
|
||||||
|
* removed by a checkpoint, so check for that to get a better
|
||||||
|
* error message.
|
||||||
|
*/
|
||||||
|
CheckXLogRemoved(segno, tli);
|
||||||
|
|
||||||
|
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)
|
|||||||
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)
|
|||||||
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:
|
|||||||
* 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);
|
|||||||
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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user