Commit 0b632913 authored by Heikki Linnakangas's avatar Heikki Linnakangas

Make pg_receivexlog and pg_basebackup -X stream work across timeline switches.

This mirrors the changes done earlier to the server in standby mode. When
receivelog reaches the end of a timeline, as reported by the server, it
fetches the timeline history file of the next timeline, and restarts
streaming from the new timeline by issuing a new START_STREAMING command.

When pg_receivexlog crosses a timeline, it leaves the .partial suffix on the
last segment on the old timeline. This helps you to tell apart a partial
segment left in the directory because of a timeline switch, and a completed
segment. If you just follow a single server, it won't make a difference, but
it can be significant in more complicated scenarios where new WAL is still
generated on the old timeline.

This includes two small changes to the streaming replication protocol:
First, when you reach the end of timeline while streaming, the server now
sends the TLI of the next timeline in the server's history to the client.
pg_receivexlog uses that as the next timeline, so that it doesn't need to
parse the timeline history file like a standby server does. Second, when
BASE_BACKUP command sends the begin and end WAL positions, it now also sends
the timeline IDs corresponding the positions.
parent 8ae35e91
...@@ -1418,8 +1418,10 @@ The commands accepted in walsender mode are: ...@@ -1418,8 +1418,10 @@ The commands accepted in walsender mode are:
<para> <para>
After streaming all the WAL on a timeline that is not the latest one, After streaming all the WAL on a timeline that is not the latest one,
the server will end streaming by exiting the COPY mode. When the client the server will end streaming by exiting the COPY mode. When the client
acknowledges this by also exiting COPY mode, the server responds with a acknowledges this by also exiting COPY mode, the server sends a
CommandComplete message, and is ready to accept a new command. single-row, single-column result set indicating the next timeline in
this server's history. That is followed by a CommandComplete message,
and the server is ready to accept a new command.
</para> </para>
<para> <para>
...@@ -1784,7 +1786,9 @@ The commands accepted in walsender mode are: ...@@ -1784,7 +1786,9 @@ The commands accepted in walsender mode are:
</para> </para>
<para> <para>
The first ordinary result set contains the starting position of the The first ordinary result set contains the starting position of the
backup, given in XLogRecPtr format as a single column in a single row. backup, in a single row with two columns. The first column contains
the start position given in XLogRecPtr format, and the second column
contains the corresponding timeline ID.
</para> </para>
<para> <para>
The second ordinary result set has one row for each tablespace. The second ordinary result set has one row for each tablespace.
...@@ -1827,7 +1831,9 @@ The commands accepted in walsender mode are: ...@@ -1827,7 +1831,9 @@ The commands accepted in walsender mode are:
<quote>ustar interchange format</> specified in the POSIX 1003.1-2008 <quote>ustar interchange format</> specified in the POSIX 1003.1-2008
standard) dump of the tablespace contents, except that the two trailing standard) dump of the tablespace contents, except that the two trailing
blocks of zeroes specified in the standard are omitted. blocks of zeroes specified in the standard are omitted.
After the tar data is complete, a final ordinary result set will be sent. After the tar data is complete, a final ordinary result set will be sent,
containing the WAL end position of the backup, in the same format as
the start position.
</para> </para>
<para> <para>
......
...@@ -545,22 +545,26 @@ tliOfPointInHistory(XLogRecPtr ptr, List *history) ...@@ -545,22 +545,26 @@ tliOfPointInHistory(XLogRecPtr ptr, List *history)
} }
/* /*
* Returns the point in history where we branched off the given timeline. * Returns the point in history where we branched off the given timeline,
* Returns InvalidXLogRecPtr if the timeline is current (= we have not * and the timeline we branched to (*nextTLI). Returns InvalidXLogRecPtr if
* branched off from it), and throws an error if the timeline is not part of * the timeline is current, ie. we have not branched off from it, and throws
* this server's history. * an error if the timeline is not part of this server's history.
*/ */
XLogRecPtr XLogRecPtr
tliSwitchPoint(TimeLineID tli, List *history) tliSwitchPoint(TimeLineID tli, List *history, TimeLineID *nextTLI)
{ {
ListCell *cell; ListCell *cell;
if (nextTLI)
*nextTLI = 0;
foreach (cell, history) foreach (cell, history)
{ {
TimeLineHistoryEntry *tle = (TimeLineHistoryEntry *) lfirst(cell); TimeLineHistoryEntry *tle = (TimeLineHistoryEntry *) lfirst(cell);
if (tle->tli == tli) if (tle->tli == tli)
return tle->end; return tle->end;
if (nextTLI)
*nextTLI = tle->tli;
} }
ereport(ERROR, ereport(ERROR,
......
...@@ -4930,7 +4930,7 @@ StartupXLOG(void) ...@@ -4930,7 +4930,7 @@ StartupXLOG(void)
* tliSwitchPoint will throw an error if the checkpoint's timeline * tliSwitchPoint will throw an error if the checkpoint's timeline
* is not in expectedTLEs at all. * is not in expectedTLEs at all.
*/ */
switchpoint = tliSwitchPoint(ControlFile->checkPointCopy.ThisTimeLineID, expectedTLEs); switchpoint = tliSwitchPoint(ControlFile->checkPointCopy.ThisTimeLineID, expectedTLEs, NULL);
ereport(FATAL, ereport(FATAL,
(errmsg("requested timeline %u is not a child of this server's history", (errmsg("requested timeline %u is not a child of this server's history",
recoveryTargetTLI), recoveryTargetTLI),
...@@ -7870,16 +7870,21 @@ XLogFileNameP(TimeLineID tli, XLogSegNo segno) ...@@ -7870,16 +7870,21 @@ XLogFileNameP(TimeLineID tli, XLogSegNo segno)
* non-exclusive backups active at the same time, and they don't conflict * non-exclusive backups active at the same time, and they don't conflict
* with an exclusive backup either. * with an exclusive backup either.
* *
* Returns the minimum WAL position that must be present to restore from this
* backup, and the corresponding timeline ID in *starttli_p.
*
* Every successfully started non-exclusive backup must be stopped by calling * Every successfully started non-exclusive backup must be stopped by calling
* do_pg_stop_backup() or do_pg_abort_backup(). * do_pg_stop_backup() or do_pg_abort_backup().
*/ */
XLogRecPtr XLogRecPtr
do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile) do_pg_start_backup(const char *backupidstr, bool fast, TimeLineID *starttli_p,
char **labelfile)
{ {
bool exclusive = (labelfile == NULL); bool exclusive = (labelfile == NULL);
bool backup_started_in_recovery = false; bool backup_started_in_recovery = false;
XLogRecPtr checkpointloc; XLogRecPtr checkpointloc;
XLogRecPtr startpoint; XLogRecPtr startpoint;
TimeLineID starttli;
pg_time_t stamp_time; pg_time_t stamp_time;
char strfbuf[128]; char strfbuf[128];
char xlogfilename[MAXFNAMELEN]; char xlogfilename[MAXFNAMELEN];
...@@ -8021,6 +8026,7 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile) ...@@ -8021,6 +8026,7 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
LWLockAcquire(ControlFileLock, LW_SHARED); LWLockAcquire(ControlFileLock, LW_SHARED);
checkpointloc = ControlFile->checkPoint; checkpointloc = ControlFile->checkPoint;
startpoint = ControlFile->checkPointCopy.redo; startpoint = ControlFile->checkPointCopy.redo;
starttli = ControlFile->checkPointCopy.ThisTimeLineID;
checkpointfpw = ControlFile->checkPointCopy.fullPageWrites; checkpointfpw = ControlFile->checkPointCopy.fullPageWrites;
LWLockRelease(ControlFileLock); LWLockRelease(ControlFileLock);
...@@ -8154,6 +8160,8 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile) ...@@ -8154,6 +8160,8 @@ do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile)
/* /*
* We're done. As a convenience, return the starting WAL location. * We're done. As a convenience, return the starting WAL location.
*/ */
if (starttli_p)
*starttli_p = starttli;
return startpoint; return startpoint;
} }
...@@ -8190,14 +8198,18 @@ pg_start_backup_callback(int code, Datum arg) ...@@ -8190,14 +8198,18 @@ pg_start_backup_callback(int code, Datum arg)
* If labelfile is NULL, this stops an exclusive backup. Otherwise this stops * If labelfile is NULL, this stops an exclusive backup. Otherwise this stops
* the non-exclusive backup specified by 'labelfile'. * the non-exclusive backup specified by 'labelfile'.
*
* Returns the last WAL position that must be present to restore from this
* backup, and the corresponding timeline ID in *stoptli_p.
*/ */
XLogRecPtr XLogRecPtr
do_pg_stop_backup(char *labelfile, bool waitforarchive) do_pg_stop_backup(char *labelfile, bool waitforarchive, TimeLineID *stoptli_p)
{ {
bool exclusive = (labelfile == NULL); bool exclusive = (labelfile == NULL);
bool backup_started_in_recovery = false; bool backup_started_in_recovery = false;
XLogRecPtr startpoint; XLogRecPtr startpoint;
XLogRecPtr stoppoint; XLogRecPtr stoppoint;
TimeLineID stoptli;
XLogRecData rdata; XLogRecData rdata;
pg_time_t stamp_time; pg_time_t stamp_time;
char strfbuf[128]; char strfbuf[128];
...@@ -8401,8 +8413,11 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive) ...@@ -8401,8 +8413,11 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive)
LWLockAcquire(ControlFileLock, LW_SHARED); LWLockAcquire(ControlFileLock, LW_SHARED);
stoppoint = ControlFile->minRecoveryPoint; stoppoint = ControlFile->minRecoveryPoint;
stoptli = ControlFile->minRecoveryPointTLI;
LWLockRelease(ControlFileLock); LWLockRelease(ControlFileLock);
if (stoptli_p)
*stoptli_p = stoptli;
return stoppoint; return stoppoint;
} }
...@@ -8414,6 +8429,7 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive) ...@@ -8414,6 +8429,7 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive)
rdata.buffer = InvalidBuffer; rdata.buffer = InvalidBuffer;
rdata.next = NULL; rdata.next = NULL;
stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END, &rdata); stoppoint = XLogInsert(RM_XLOG_ID, XLOG_BACKUP_END, &rdata);
stoptli = ThisTimeLineID;
/* /*
* Force a switch to a new xlog segment file, so that the backup is valid * Force a switch to a new xlog segment file, so that the backup is valid
...@@ -8529,6 +8545,8 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive) ...@@ -8529,6 +8545,8 @@ do_pg_stop_backup(char *labelfile, bool waitforarchive)
/* /*
* We're done. As a convenience, return the ending WAL location. * We're done. As a convenience, return the ending WAL location.
*/ */
if (stoptli_p)
*stoptli_p = stoptli;
return stoppoint; return stoppoint;
} }
......
...@@ -56,7 +56,7 @@ pg_start_backup(PG_FUNCTION_ARGS) ...@@ -56,7 +56,7 @@ pg_start_backup(PG_FUNCTION_ARGS)
backupidstr = text_to_cstring(backupid); backupidstr = text_to_cstring(backupid);
startpoint = do_pg_start_backup(backupidstr, fast, NULL); startpoint = do_pg_start_backup(backupidstr, fast, NULL, NULL);
snprintf(startxlogstr, sizeof(startxlogstr), "%X/%X", snprintf(startxlogstr, sizeof(startxlogstr), "%X/%X",
(uint32) (startpoint >> 32), (uint32) startpoint); (uint32) (startpoint >> 32), (uint32) startpoint);
...@@ -82,7 +82,7 @@ pg_stop_backup(PG_FUNCTION_ARGS) ...@@ -82,7 +82,7 @@ pg_stop_backup(PG_FUNCTION_ARGS)
XLogRecPtr stoppoint; XLogRecPtr stoppoint;
char stopxlogstr[MAXFNAMELEN]; char stopxlogstr[MAXFNAMELEN];
stoppoint = do_pg_stop_backup(NULL, true); stoppoint = do_pg_stop_backup(NULL, true, NULL);
snprintf(stopxlogstr, sizeof(stopxlogstr), "%X/%X", snprintf(stopxlogstr, sizeof(stopxlogstr), "%X/%X",
(uint32) (stoppoint >> 32), (uint32) stoppoint); (uint32) (stoppoint >> 32), (uint32) stoppoint);
......
...@@ -55,7 +55,7 @@ static void SendBackupHeader(List *tablespaces); ...@@ -55,7 +55,7 @@ static void SendBackupHeader(List *tablespaces);
static void base_backup_cleanup(int code, Datum arg); 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, TimeLineID tli);
static int compareWalFileNames(const void *a, const void *b); 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? */
...@@ -94,13 +94,16 @@ static void ...@@ -94,13 +94,16 @@ static void
perform_base_backup(basebackup_options *opt, DIR *tblspcdir) perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
{ {
XLogRecPtr startptr; XLogRecPtr startptr;
TimeLineID starttli;
XLogRecPtr endptr; XLogRecPtr endptr;
TimeLineID endtli;
char *labelfile; char *labelfile;
backup_started_in_recovery = RecoveryInProgress(); backup_started_in_recovery = RecoveryInProgress();
startptr = do_pg_start_backup(opt->label, opt->fastcheckpoint, &labelfile); startptr = do_pg_start_backup(opt->label, opt->fastcheckpoint, &starttli,
SendXlogRecPtrResult(startptr); &labelfile);
SendXlogRecPtrResult(startptr, starttli);
PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0); PG_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
{ {
...@@ -218,7 +221,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir) ...@@ -218,7 +221,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
} }
PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0); PG_END_ENSURE_ERROR_CLEANUP(base_backup_cleanup, (Datum) 0);
endptr = do_pg_stop_backup(labelfile, !opt->nowait); endptr = do_pg_stop_backup(labelfile, !opt->nowait, &endtli);
if (opt->includewal) if (opt->includewal)
{ {
...@@ -426,7 +429,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir) ...@@ -426,7 +429,7 @@ perform_base_backup(basebackup_options *opt, DIR *tblspcdir)
/* Send CopyDone message for the last tar file */ /* Send CopyDone message for the last tar file */
pq_putemptymessage('c'); pq_putemptymessage('c');
} }
SendXlogRecPtrResult(endptr); SendXlogRecPtrResult(endptr, endtli);
} }
/* /*
...@@ -635,17 +638,15 @@ SendBackupHeader(List *tablespaces) ...@@ -635,17 +638,15 @@ SendBackupHeader(List *tablespaces)
* XlogRecPtr record (in text format) * XlogRecPtr record (in text format)
*/ */
static void static void
SendXlogRecPtrResult(XLogRecPtr ptr) SendXlogRecPtrResult(XLogRecPtr ptr, TimeLineID tli)
{ {
StringInfoData buf; StringInfoData buf;
char str[MAXFNAMELEN]; char str[MAXFNAMELEN];
snprintf(str, sizeof(str), "%X/%X", (uint32) (ptr >> 32), (uint32) ptr);
pq_beginmessage(&buf, 'T'); /* RowDescription */ pq_beginmessage(&buf, 'T'); /* RowDescription */
pq_sendint(&buf, 1, 2); /* 1 field */ pq_sendint(&buf, 2, 2); /* 2 fields */
/* Field header */ /* Field headers */
pq_sendstring(&buf, "recptr"); pq_sendstring(&buf, "recptr");
pq_sendint(&buf, 0, 4); /* table oid */ pq_sendint(&buf, 0, 4); /* table oid */
pq_sendint(&buf, 0, 2); /* attnum */ pq_sendint(&buf, 0, 2); /* attnum */
...@@ -653,11 +654,29 @@ SendXlogRecPtrResult(XLogRecPtr ptr) ...@@ -653,11 +654,29 @@ SendXlogRecPtrResult(XLogRecPtr ptr)
pq_sendint(&buf, -1, 2); pq_sendint(&buf, -1, 2);
pq_sendint(&buf, 0, 4); pq_sendint(&buf, 0, 4);
pq_sendint(&buf, 0, 2); pq_sendint(&buf, 0, 2);
pq_sendstring(&buf, "tli");
pq_sendint(&buf, 0, 4); /* table oid */
pq_sendint(&buf, 0, 2); /* attnum */
/*
* int8 may seem like a surprising data type for this, but in thory int4
* would not be wide enough for this, as TimeLineID is unsigned.
*/
pq_sendint(&buf, INT8OID, 4); /* type oid */
pq_sendint(&buf, -1, 2);
pq_sendint(&buf, 0, 4);
pq_sendint(&buf, 0, 2);
pq_endmessage(&buf); pq_endmessage(&buf);
/* Data row */ /* Data row */
pq_beginmessage(&buf, 'D'); pq_beginmessage(&buf, 'D');
pq_sendint(&buf, 1, 2); /* number of columns */ pq_sendint(&buf, 2, 2); /* number of columns */
snprintf(str, sizeof(str), "%X/%X", (uint32) (ptr >> 32), (uint32) ptr);
pq_sendint(&buf, strlen(str), 4); /* length */
pq_sendbytes(&buf, str, strlen(str));
snprintf(str, sizeof(str), "%u", tli);
pq_sendint(&buf, strlen(str), 4); /* length */ pq_sendint(&buf, strlen(str), 4); /* length */
pq_sendbytes(&buf, str, strlen(str)); pq_sendbytes(&buf, str, strlen(str));
pq_endmessage(&buf); pq_endmessage(&buf);
......
...@@ -117,6 +117,7 @@ static uint32 sendOff = 0; ...@@ -117,6 +117,7 @@ static uint32 sendOff = 0;
* history forked off from that timeline at sendTimeLineValidUpto. * history forked off from that timeline at sendTimeLineValidUpto.
*/ */
static TimeLineID sendTimeLine = 0; static TimeLineID sendTimeLine = 0;
static TimeLineID sendTimeLineNextTLI = 0;
static bool sendTimeLineIsHistoric = false; static bool sendTimeLineIsHistoric = false;
static XLogRecPtr sendTimeLineValidUpto = InvalidXLogRecPtr; static XLogRecPtr sendTimeLineValidUpto = InvalidXLogRecPtr;
...@@ -449,7 +450,8 @@ StartReplication(StartReplicationCmd *cmd) ...@@ -449,7 +450,8 @@ StartReplication(StartReplicationCmd *cmd)
* requested start location is on that timeline. * requested start location is on that timeline.
*/ */
timeLineHistory = readTimeLineHistory(ThisTimeLineID); timeLineHistory = readTimeLineHistory(ThisTimeLineID);
switchpoint = tliSwitchPoint(cmd->timeline, timeLineHistory); switchpoint = tliSwitchPoint(cmd->timeline, timeLineHistory,
&sendTimeLineNextTLI);
list_free_deep(timeLineHistory); list_free_deep(timeLineHistory);
/* /*
...@@ -496,8 +498,7 @@ StartReplication(StartReplicationCmd *cmd) ...@@ -496,8 +498,7 @@ StartReplication(StartReplicationCmd *cmd)
streamingDoneSending = streamingDoneReceiving = false; streamingDoneSending = streamingDoneReceiving = false;
/* If there is nothing to stream, don't even enter COPY mode */ /* If there is nothing to stream, don't even enter COPY mode */
if (!sendTimeLineIsHistoric || if (!sendTimeLineIsHistoric || cmd->startpoint < sendTimeLineValidUpto)
cmd->startpoint < sendTimeLineValidUpto)
{ {
/* /*
* When we first start replication the standby will be behind the primary. * When we first start replication the standby will be behind the primary.
...@@ -554,10 +555,46 @@ StartReplication(StartReplicationCmd *cmd) ...@@ -554,10 +555,46 @@ StartReplication(StartReplicationCmd *cmd)
if (walsender_ready_to_stop) if (walsender_ready_to_stop)
proc_exit(0); proc_exit(0);
WalSndSetState(WALSNDSTATE_STARTUP); WalSndSetState(WALSNDSTATE_STARTUP);
Assert(streamingDoneSending && streamingDoneReceiving);
}
/*
* Copy is finished now. Send a single-row result set indicating the next
* timeline.
*/
if (sendTimeLineIsHistoric)
{
char str[11];
snprintf(str, sizeof(str), "%u", sendTimeLineNextTLI);
pq_beginmessage(&buf, 'T'); /* RowDescription */
pq_sendint(&buf, 1, 2); /* 1 field */
/* Field header */
pq_sendstring(&buf, "next_tli");
pq_sendint(&buf, 0, 4); /* table oid */
pq_sendint(&buf, 0, 2); /* attnum */
/*
* int8 may seem like a surprising data type for this, but in theory
* int4 would not be wide enough for this, as TimeLineID is unsigned.
*/
pq_sendint(&buf, INT8OID, 4); /* type oid */
pq_sendint(&buf, -1, 2);
pq_sendint(&buf, 0, 4);
pq_sendint(&buf, 0, 2);
pq_endmessage(&buf);
/* Data row */
pq_beginmessage(&buf, 'D');
pq_sendint(&buf, 1, 2); /* number of columns */
pq_sendint(&buf, strlen(str), 4); /* length */
pq_sendbytes(&buf, str, strlen(str));
pq_endmessage(&buf);
} }
/* Get out of COPY mode (CommandComplete). */ /* Send CommandComplete message */
EndCommand("COPY 0", DestRemote); pq_puttextmessage('C', "START_STREAMING");
} }
/* /*
...@@ -1377,8 +1414,9 @@ XLogSend(bool *caughtup) ...@@ -1377,8 +1414,9 @@ XLogSend(bool *caughtup)
List *history; List *history;
history = readTimeLineHistory(ThisTimeLineID); history = readTimeLineHistory(ThisTimeLineID);
sendTimeLineValidUpto = tliSwitchPoint(sendTimeLine, history); sendTimeLineValidUpto = tliSwitchPoint(sendTimeLine, history, &sendTimeLineNextTLI);
Assert(sentPtr <= sendTimeLineValidUpto); Assert(sentPtr <= sendTimeLineValidUpto);
Assert(sendTimeLine < sendTimeLineNextTLI);
list_free_deep(history); list_free_deep(history);
/* the current send pointer should be <= the switchpoint */ /* the current send pointer should be <= the switchpoint */
......
...@@ -243,7 +243,7 @@ LogStreamerMain(logstreamer_param *param) ...@@ -243,7 +243,7 @@ LogStreamerMain(logstreamer_param *param)
if (!ReceiveXlogStream(param->bgconn, param->startptr, param->timeline, if (!ReceiveXlogStream(param->bgconn, param->startptr, param->timeline,
param->sysidentifier, param->xlogdir, param->sysidentifier, param->xlogdir,
reached_end_position, standby_message_timeout, reached_end_position, standby_message_timeout,
true)) NULL))
/* /*
* Any errors will already have been reported in the function process, * Any errors will already have been reported in the function process,
...@@ -1220,7 +1220,7 @@ BaseBackup(void) ...@@ -1220,7 +1220,7 @@ BaseBackup(void)
{ {
PGresult *res; PGresult *res;
char *sysidentifier; char *sysidentifier;
uint32 timeline; uint32 starttli;
char current_path[MAXPGPATH]; char current_path[MAXPGPATH];
char escaped_label[MAXPGPATH]; char escaped_label[MAXPGPATH];
int i; int i;
...@@ -1259,7 +1259,6 @@ BaseBackup(void) ...@@ -1259,7 +1259,6 @@ BaseBackup(void)
disconnect_and_exit(1); disconnect_and_exit(1);
} }
sysidentifier = pg_strdup(PQgetvalue(res, 0, 0)); sysidentifier = pg_strdup(PQgetvalue(res, 0, 0));
timeline = atoi(PQgetvalue(res, 0, 1));
PQclear(res); PQclear(res);
/* /*
...@@ -1291,18 +1290,24 @@ BaseBackup(void) ...@@ -1291,18 +1290,24 @@ BaseBackup(void)
progname, PQerrorMessage(conn)); progname, PQerrorMessage(conn));
disconnect_and_exit(1); disconnect_and_exit(1);
} }
if (PQntuples(res) != 1) if (PQntuples(res) != 1 || PQnfields(res) < 2)
{ {
fprintf(stderr, _("%s: no start point returned from server\n"), fprintf(stderr,
progname); _("%s: server returned unexpected response to BASE_BACKUP command; got %d rows and %d fields, expected %d rows and %d fields\n"),
progname, PQntuples(res), PQnfields(res), 1, 2);
disconnect_and_exit(1); disconnect_and_exit(1);
} }
strcpy(xlogstart, PQgetvalue(res, 0, 0)); strcpy(xlogstart, PQgetvalue(res, 0, 0));
if (verbose && includewal) starttli = atoi(PQgetvalue(res, 0, 1));
fprintf(stderr, "transaction log start point: %s\n", xlogstart);
PQclear(res); PQclear(res);
MemSet(xlogend, 0, sizeof(xlogend)); MemSet(xlogend, 0, sizeof(xlogend));
if (verbose && includewal)
fprintf(stderr, _("transaction log start point: %s on timeline %u\n"),
xlogstart, starttli);
/* /*
* Get the header * Get the header
*/ */
...@@ -1358,7 +1363,7 @@ BaseBackup(void) ...@@ -1358,7 +1363,7 @@ BaseBackup(void)
if (verbose) if (verbose)
fprintf(stderr, _("%s: starting background WAL receiver\n"), fprintf(stderr, _("%s: starting background WAL receiver\n"),
progname); progname);
StartLogStreamer(xlogstart, timeline, sysidentifier); StartLogStreamer(xlogstart, starttli, sysidentifier);
} }
/* /*
......
...@@ -39,8 +39,7 @@ volatile bool time_to_abort = false; ...@@ -39,8 +39,7 @@ volatile bool time_to_abort = false;
static void usage(void); static void usage(void);
static XLogRecPtr FindStreamingStart(XLogRecPtr currentpos, static XLogRecPtr FindStreamingStart(uint32 *tli);
uint32 currenttimeline);
static void StreamLog(); static void StreamLog();
static bool stop_streaming(XLogRecPtr segendpos, uint32 timeline, static bool stop_streaming(XLogRecPtr segendpos, uint32 timeline,
bool segment_finished); bool segment_finished);
...@@ -70,14 +69,31 @@ usage(void) ...@@ -70,14 +69,31 @@ usage(void)
} }
static bool static bool
stop_streaming(XLogRecPtr segendpos, uint32 timeline, bool segment_finished) stop_streaming(XLogRecPtr xlogpos, uint32 timeline, bool segment_finished)
{ {
static uint32 prevtimeline = 0;
static XLogRecPtr prevpos = InvalidXLogRecPtr;
/* we assume that we get called once at the end of each segment */
if (verbose && segment_finished) if (verbose && segment_finished)
fprintf(stderr, _("%s: finished segment at %X/%X (timeline %u)\n"), fprintf(stderr, _("%s: finished segment at %X/%X (timeline %u)\n"),
progname, progname, (uint32) (xlogpos >> 32), (uint32) xlogpos,
(uint32) (segendpos >> 32), (uint32) segendpos,
timeline); timeline);
/*
* Note that we report the previous, not current, position here. That's
* the exact location where the timeline switch happend. After the switch,
* we restart streaming from the beginning of the segment, so xlogpos can
* smaller than prevpos if we just switched to new timeline.
*/
if (prevtimeline != 0 && prevtimeline != timeline)
fprintf(stderr, _("%s: switched to timeline %u at %X/%X\n"),
progname, timeline,
(uint32) (prevpos >> 32), (uint32) prevpos);
prevtimeline = timeline;
prevpos = xlogpos;
if (time_to_abort) if (time_to_abort)
{ {
fprintf(stderr, _("%s: received interrupt signal, exiting\n"), fprintf(stderr, _("%s: received interrupt signal, exiting\n"),
...@@ -88,20 +104,19 @@ stop_streaming(XLogRecPtr segendpos, uint32 timeline, bool segment_finished) ...@@ -88,20 +104,19 @@ stop_streaming(XLogRecPtr segendpos, uint32 timeline, bool segment_finished)
} }
/* /*
* Determine starting location for streaming, based on: * Determine starting location for streaming, based on any existing xlog
* 1. If there are existing xlog segments, start at the end of the last one * segments in the directory. We start at the end of the last one that is
* that is complete (size matches XLogSegSize) * complete (size matches XLogSegSize), on the timeline with highest ID.
* 2. If no valid xlog exists, start from the beginning of the current *
* WAL segment. * If there are no WAL files in the directory, returns InvalidXLogRecPtr.
*/ */
static XLogRecPtr static XLogRecPtr
FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline) FindStreamingStart(uint32 *tli)
{ {
DIR *dir; DIR *dir;
struct dirent *dirent; struct dirent *dirent;
int i;
bool b;
XLogSegNo high_segno = 0; XLogSegNo high_segno = 0;
uint32 high_tli = 0;
dir = opendir(basedir); dir = opendir(basedir);
if (dir == NULL) if (dir == NULL)
...@@ -120,26 +135,13 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline) ...@@ -120,26 +135,13 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline)
seg; seg;
XLogSegNo segno; XLogSegNo segno;
if (strcmp(dirent->d_name, ".") == 0 || /*
strcmp(dirent->d_name, "..") == 0) * Check if the filename looks like an xlog file, or a .partial file.
continue; * Xlog files are always 24 characters, and .partial files are 32
* characters.
/* xlog files are always 24 characters */ */
if (strlen(dirent->d_name) != 24) if (strlen(dirent->d_name) != 24 ||
continue; !strspn(dirent->d_name, "0123456789ABCDEF") == 24)
/* Filenames are always made out of 0-9 and A-F */
b = false;
for (i = 0; i < 24; i++)
{
if (!(dirent->d_name[i] >= '0' && dirent->d_name[i] <= '9') &&
!(dirent->d_name[i] >= 'A' && dirent->d_name[i] <= 'F'))
{
b = true;
break;
}
}
if (b)
continue; continue;
/* /*
...@@ -154,10 +156,6 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline) ...@@ -154,10 +156,6 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline)
} }
segno = ((uint64) log) << 32 | seg; segno = ((uint64) log) << 32 | seg;
/* Ignore any files that are for another timeline */
if (tli != currenttimeline)
continue;
/* Check if this is a completed segment or not */ /* Check if this is a completed segment or not */
snprintf(fullpath, sizeof(fullpath), "%s/%s", basedir, dirent->d_name); snprintf(fullpath, sizeof(fullpath), "%s/%s", basedir, dirent->d_name);
if (stat(fullpath, &statbuf) != 0) if (stat(fullpath, &statbuf) != 0)
...@@ -170,9 +168,10 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline) ...@@ -170,9 +168,10 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline)
if (statbuf.st_size == XLOG_SEG_SIZE) if (statbuf.st_size == XLOG_SEG_SIZE)
{ {
/* Completed segment */ /* Completed segment */
if (segno > high_segno) if (segno > high_segno || (segno == high_segno && tli > high_tli))
{ {
high_segno = segno; high_segno = segno;
high_tli = tli;
continue; continue;
} }
} }
...@@ -199,10 +198,11 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline) ...@@ -199,10 +198,11 @@ FindStreamingStart(XLogRecPtr currentpos, uint32 currenttimeline)
XLogSegNoOffsetToRecPtr(high_segno, 0, high_ptr); XLogSegNoOffsetToRecPtr(high_segno, 0, high_ptr);
*tli = high_tli;
return high_ptr; return high_ptr;
} }
else else
return currentpos; return InvalidXLogRecPtr;
} }
/* /*
...@@ -212,8 +212,10 @@ static void ...@@ -212,8 +212,10 @@ static void
StreamLog(void) StreamLog(void)
{ {
PGresult *res; PGresult *res;
uint32 timeline;
XLogRecPtr startpos; XLogRecPtr startpos;
uint32 starttli;
XLogRecPtr serverpos;
uint32 servertli;
uint32 hi, uint32 hi,
lo; lo;
...@@ -243,7 +245,7 @@ StreamLog(void) ...@@ -243,7 +245,7 @@ StreamLog(void)
progname, PQntuples(res), PQnfields(res), 1, 3); progname, PQntuples(res), PQnfields(res), 1, 3);
disconnect_and_exit(1); disconnect_and_exit(1);
} }
timeline = atoi(PQgetvalue(res, 0, 1)); servertli = atoi(PQgetvalue(res, 0, 1));
if (sscanf(PQgetvalue(res, 0, 2), "%X/%X", &hi, &lo) != 2) if (sscanf(PQgetvalue(res, 0, 2), "%X/%X", &hi, &lo) != 2)
{ {
fprintf(stderr, fprintf(stderr,
...@@ -251,13 +253,18 @@ StreamLog(void) ...@@ -251,13 +253,18 @@ StreamLog(void)
progname, PQgetvalue(res, 0, 2)); progname, PQgetvalue(res, 0, 2));
disconnect_and_exit(1); disconnect_and_exit(1);
} }
startpos = ((uint64) hi) << 32 | lo; serverpos = ((uint64) hi) << 32 | lo;
PQclear(res); PQclear(res);
/* /*
* Figure out where to start streaming. * Figure out where to start streaming.
*/ */
startpos = FindStreamingStart(startpos, timeline); startpos = FindStreamingStart(&starttli);
if (startpos == InvalidXLogRecPtr)
{
startpos = serverpos;
starttli = servertli;
}
/* /*
* Always start streaming at the beginning of a segment * Always start streaming at the beginning of a segment
...@@ -271,10 +278,10 @@ StreamLog(void) ...@@ -271,10 +278,10 @@ StreamLog(void)
fprintf(stderr, fprintf(stderr,
_("%s: starting log streaming at %X/%X (timeline %u)\n"), _("%s: starting log streaming at %X/%X (timeline %u)\n"),
progname, (uint32) (startpos >> 32), (uint32) startpos, progname, (uint32) (startpos >> 32), (uint32) startpos,
timeline); starttli);
ReceiveXlogStream(conn, startpos, timeline, NULL, basedir, ReceiveXlogStream(conn, startpos, starttli, NULL, basedir,
stop_streaming, standby_message_timeout, false); stop_streaming, standby_message_timeout, ".partial");
PQfinish(conn); PQfinish(conn);
} }
......
This diff is collapsed.
...@@ -13,4 +13,4 @@ extern bool ReceiveXlogStream(PGconn *conn, ...@@ -13,4 +13,4 @@ extern bool ReceiveXlogStream(PGconn *conn,
char *basedir, char *basedir,
stream_stop_callback stream_stop, stream_stop_callback stream_stop,
int standby_message_timeout, int standby_message_timeout,
bool rename_partial); char *partial_suffix);
...@@ -37,6 +37,7 @@ extern void writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI, ...@@ -37,6 +37,7 @@ extern void writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
extern void writeTimeLineHistoryFile(TimeLineID tli, char *content, int size); extern void writeTimeLineHistoryFile(TimeLineID tli, char *content, int size);
extern bool tliInHistory(TimeLineID tli, List *expectedTLIs); extern bool tliInHistory(TimeLineID tli, List *expectedTLIs);
extern TimeLineID tliOfPointInHistory(XLogRecPtr ptr, List *history); extern TimeLineID tliOfPointInHistory(XLogRecPtr ptr, List *history);
extern XLogRecPtr tliSwitchPoint(TimeLineID tli, List *history); extern XLogRecPtr tliSwitchPoint(TimeLineID tli, List *history,
TimeLineID *nextTLI);
#endif /* TIMELINE_H */ #endif /* TIMELINE_H */
...@@ -317,8 +317,10 @@ extern void SetWalWriterSleeping(bool sleeping); ...@@ -317,8 +317,10 @@ extern void SetWalWriterSleeping(bool sleeping);
/* /*
* Starting/stopping a base backup * Starting/stopping a base backup
*/ */
extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast, char **labelfile); extern XLogRecPtr do_pg_start_backup(const char *backupidstr, bool fast,
extern XLogRecPtr do_pg_stop_backup(char *labelfile, bool waitforarchive); TimeLineID *starttli_p, char **labelfile);
extern XLogRecPtr do_pg_stop_backup(char *labelfile, bool waitforarchive,
TimeLineID *stoptli_p);
extern void do_pg_abort_backup(void); extern void do_pg_abort_backup(void);
/* File path names (all relative to $PGDATA) */ /* File path names (all relative to $PGDATA) */
......
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