mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
Fix bugs in cascading replication with recovery_target_timeline='latest'
The cascading replication code assumed that the current RecoveryTargetTLI never changes, but that's not true with recovery_target_timeline='latest'. The obvious upshot of that is that RecoveryTargetTLI in shared memory needs to be protected by a lock. A less obvious consequence is that when a cascading standby is connected, and the standby switches to a new target timeline after scanning the archive, it will continue to stream WAL to the cascading standby, but from a wrong file, ie. the file of the previous timeline. For example, if the standby is currently streaming from the middle of file 000000010000000000000005, and the timeline changes, the standby will continue to stream from that file. However, the WAL on the new timeline is in file 000000020000000000000005, so the standby sends garbage from 000000010000000000000005 to the cascading standby, instead of the correct WAL from file 000000020000000000000005. This also fixes a related bug where a partial WAL segment is restored from the archive and streamed to a cascading standby. The code assumed that when a WAL segment is copied from the archive, it can immediately be fully streamed to a cascading standby. However, if the segment is only partially filled, ie. has the right size, but only N first bytes contain valid WAL, that's not safe. That can happen if a partial WAL segment is manually copied to the archive, or if a partial WAL segment is archived because a server is started up on a new timeline within that segment. The cascading standby will get confused if the WAL it received is not valid, and will get stuck until it's restarted. This patch fixes that problem by not allowing WAL restored from the archive to be streamed to a cascading standby until it's been replayed, and thus validated.
This commit is contained in:
@ -303,7 +303,7 @@ IdentifySystem(void)
|
||||
GetSystemIdentifier());
|
||||
snprintf(tli, sizeof(tli), "%u", ThisTimeLineID);
|
||||
|
||||
logptr = am_cascading_walsender ? GetStandbyFlushRecPtr() : GetInsertRecPtr();
|
||||
logptr = am_cascading_walsender ? GetStandbyFlushRecPtr(NULL) : GetInsertRecPtr();
|
||||
|
||||
snprintf(xpos, sizeof(xpos), "%X/%X", (uint32) (logptr >> 32), (uint32) logptr);
|
||||
|
||||
@ -1137,7 +1137,31 @@ XLogSend(char *msgbuf, bool *caughtup)
|
||||
* subsequently crashes and restarts, slaves must not have applied any WAL
|
||||
* that gets lost on the master.
|
||||
*/
|
||||
SendRqstPtr = am_cascading_walsender ? GetStandbyFlushRecPtr() : GetFlushRecPtr();
|
||||
if (am_cascading_walsender)
|
||||
{
|
||||
TimeLineID currentTargetTLI;
|
||||
SendRqstPtr = GetStandbyFlushRecPtr(¤tTargetTLI);
|
||||
|
||||
/*
|
||||
* If the recovery target timeline changed, bail out. It's a bit
|
||||
* unfortunate that we have to just disconnect, but there is no way
|
||||
* to tell the client that the timeline changed. We also don't know
|
||||
* exactly where the switch happened, so we cannot safely try to send
|
||||
* up to the switchover point before disconnecting.
|
||||
*/
|
||||
if (currentTargetTLI != ThisTimeLineID)
|
||||
{
|
||||
if (!walsender_ready_to_stop)
|
||||
ereport(LOG,
|
||||
(errmsg("terminating walsender process to force cascaded standby "
|
||||
"to update timeline and reconnect")));
|
||||
walsender_ready_to_stop = true;
|
||||
*caughtup = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
SendRqstPtr = GetFlushRecPtr();
|
||||
|
||||
/* Quick exit if nothing to do */
|
||||
if (XLByteLE(SendRqstPtr, sentPtr))
|
||||
|
Reference in New Issue
Block a user