Thread
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Fix scenario where streaming standby gets stuck at a continuation record.
- 066871980183 11.0 cited
-
Allow a streaming replication standby to follow a timeline switch.
- abfd192b1b5b 9.3.0 cited
-
BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-01-28T17:03:24Z
Hi hackers, I've encountered a bug in PostgreSQL's streaming replication where cascading standbys fail to reconnect after falling back to archive recovery. The issue occurs when the upstream standby uses archive-only recovery. The standby requests streaming from the wrong WAL position (next segment boundary instead of the current position), causing connection failures with this error: ERROR: requested starting point 0/A000000 is ahead of the WAL flush position of this server 0/9000000 Attached are two shell scripts that reliably reproduce the issue on PostgreSQL 17.x and 18.x: 1. reproducer_restart_upstream_portable.sh - triggers by restarting upstream 2. reproducer_cascade_restart_portable.sh - triggers by restarting the cascade The scripts set up this topology: - Primary with archiving enabled - Standby using only archive recovery (no streaming from primary) - Cascading standby streaming from the archive-only standby When the cascade loses its streaming connection and falls back to archive recovery, it cannot reconnect. The issue appears to be in xlogrecovery.c around line 3880, where the position passed to RequestXLogStreaming() determines which segment boundary is requested. The cascade restart reproducer shows that even restarting the cascade itself triggers the bug, which affects routine maintenance operations. Scripts require PostgreSQL binaries in PATH and use ports 15432-15434. Best regards, Marco -
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Fujii Masao <masao.fujii@gmail.com> — 2026-01-29T11:33:11Z
On Thu, Jan 29, 2026 at 2:03 AM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Hi hackers, > > I've encountered a bug in PostgreSQL's streaming replication where cascading > standbys fail to reconnect after falling back to archive recovery. The issue > occurs when the upstream standby uses archive-only recovery. > > The standby requests streaming from the wrong WAL position (next segment boundary > instead of the current position), causing connection failures with this error: > > ERROR: requested starting point 0/A000000 is ahead of the WAL flush > position of this server 0/9000000 Thanks for the report! I was also able to reproduce this issue on the master branch. Interestingly, I couldn't reproduce it on v11 using the same test case. This makes me wonder whether the issue was introduced in v12 or later. Do you see the same behavior in your environment? Regards, -- Fujii Masao
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-01-29T12:22:17Z
Hi Marco, On Thu, Jan 29, 2026 at 1:03 AM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Hi hackers, > > I've encountered a bug in PostgreSQL's streaming replication where cascading > standbys fail to reconnect after falling back to archive recovery. The issue > occurs when the upstream standby uses archive-only recovery. > > The standby requests streaming from the wrong WAL position (next segment boundary > instead of the current position), causing connection failures with this error: > > ERROR: requested starting point 0/A000000 is ahead of the WAL flush > position of this server 0/9000000 > > Attached are two shell scripts that reliably reproduce the issue on PostgreSQL > 17.x and 18.x: > > 1. reproducer_restart_upstream_portable.sh - triggers by restarting upstream > 2. reproducer_cascade_restart_portable.sh - triggers by restarting the cascade > > The scripts set up this topology: > - Primary with archiving enabled > - Standby using only archive recovery (no streaming from primary) > - Cascading standby streaming from the archive-only standby > > When the cascade loses its streaming connection and falls back to archive recovery, > it cannot reconnect. The issue appears to be in xlogrecovery.c around line 3880, > where the position passed to RequestXLogStreaming() determines which segment > boundary is requested. > > The cascade restart reproducer shows that even restarting the cascade itself > triggers the bug, which affects routine maintenance operations. > > Scripts require PostgreSQL binaries in PATH and use ports 15432-15434. > > Best regards, > Marco > Thanks for your report. I can reliably reproduce the issue on HEAD using your scripts. I’ve analyzed the problem and am proposing a patch to fix it. --- Analysis When a cascading standby streams from an archive-only upstream: 1. The upstream's GetStandbyFlushRecPtr() returns only replay position (no received-but-not-replayed buffer since there's no walreceiver) 2. When streaming ends and the cascade falls back to archive recovery, it can restore WAL segments from its own archive access 3. The cascade's read position (RecPtr) advances beyond what the upstream has replayed 4. On reconnect, the cascade requests streaming from RecPtr, which the upstream rejects as "ahead of flush position" --- Proposed Fix Track the last confirmed flush position from streaming (lastStreamedFlush) and clamp the streaming start request when it exceeds that position: - Same timeline: clamp to lastStreamedFlush if RecPtr > lastStreamedFlush - Timeline switch: fall back to timeline switchpoint as safe boundary This ensures the cascade requests from a position the upstream definitely has, rather than assuming the upstream can serve whatever the cascade restored locally from archive. I’m not a fan of using sleep in TAP tests, but I haven’t found a better way to reproduce this behavior yet. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Fujii Masao <masao.fujii@gmail.com> — 2026-01-30T03:12:38Z
On Thu, Jan 29, 2026 at 9:22 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > Thanks for your report. I can reliably reproduce the issue on HEAD > using your scripts. I’ve analyzed the problem and am proposing a patch > to fix it. > > --- Analysis > When a cascading standby streams from an archive-only upstream: > > 1. The upstream's GetStandbyFlushRecPtr() returns only replay position > (no received-but-not-replayed buffer since there's no walreceiver) > 2. When streaming ends and the cascade falls back to archive recovery, > it can restore WAL segments from its own archive access > 3. The cascade's read position (RecPtr) advances beyond what the > upstream has replayed > 4. On reconnect, the cascade requests streaming from RecPtr, which the > upstream rejects as "ahead of flush position" > > --- Proposed Fix > > Track the last confirmed flush position from streaming > (lastStreamedFlush) and clamp the streaming start request when it > exceeds that position: I haven't read the patch yet, but doesn't lastStreamedFlush represent the same LSN as tliRecPtr or replayLSN (the arguments to WaitForWALToBecomeAvailable())? If so, we may not need to introduce a new variable to track this LSN. The choice of which LSN is used as the replication start point has varied over time to handle corner cases (for example, commit 06687198018). That makes me wonder whether we should first better understand why WaitForWALToBecomeAvailable() currently uses RecPtr as the starting point. BTW, with v1 patch, I was able to reproduce the issue using the following steps: -------------------------------------------- initdb -D data mkdir arch cat <<EOF >> data/postgresql.conf archive_mode = on archive_command = 'cp %p ../arch/%f' restore_command = 'cp ../arch/%f %p' EOF pg_ctl -D data start pg_basebackup -D sby1 -c fast cp -a sby1 sby2 cat <<EOF >> sby1/postgresql.conf port = 5433 EOF touch sby1/standby.signal pg_ctl -D sby1 start cat <<EOF >> sby2/postgresql.conf port = 5434 primary_conninfo = 'port=5433' EOF touch sby2/standby.signal pg_ctl -D sby2 start pgbench -i -s2 pg_ctl -D sby2 restart -------------------------------------------- In this case, after restarting the standby connecting to another (cascading) standby, I observed the following error. FATAL: could not receive data from WAL stream: ERROR: requested starting point 0/04000000 is ahead of the WAL flush position of this server 0/03FFE8D0 Regards, -- Fujii Masao
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-01-30T06:01:09Z
Hi Fujii'san, Thanks for looking into this. On Fri, Jan 30, 2026 at 11:12 AM Fujii Masao <masao.fujii@gmail.com> wrote: > > On Thu, Jan 29, 2026 at 9:22 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > Thanks for your report. I can reliably reproduce the issue on HEAD > > using your scripts. I’ve analyzed the problem and am proposing a patch > > to fix it. > > > > --- Analysis > > When a cascading standby streams from an archive-only upstream: > > > > 1. The upstream's GetStandbyFlushRecPtr() returns only replay position > > (no received-but-not-replayed buffer since there's no walreceiver) > > 2. When streaming ends and the cascade falls back to archive recovery, > > it can restore WAL segments from its own archive access > > 3. The cascade's read position (RecPtr) advances beyond what the > > upstream has replayed > > 4. On reconnect, the cascade requests streaming from RecPtr, which the > > upstream rejects as "ahead of flush position" > > > > --- Proposed Fix > > > > Track the last confirmed flush position from streaming > > (lastStreamedFlush) and clamp the streaming start request when it > > exceeds that position: > > I haven't read the patch yet, but doesn't lastStreamedFlush represent > the same LSN as tliRecPtr or replayLSN (the arguments to > WaitForWALToBecomeAvailable())? If so, we may not need to introduce > a new variable to track this LSN. I think they refer to different types of LSNs. I don’t have access to my computer at the moment, but I’ll look into it and get back to you shortly. > The choice of which LSN is used as the replication start point has varied > over time to handle corner cases (for example, commit 06687198018). > That makes me wonder whether we should first better understand > why WaitForWALToBecomeAvailable() currently uses RecPtr as > the starting point. > > BTW, with v1 patch, I was able to reproduce the issue using the following steps: > > -------------------------------------------- > initdb -D data > mkdir arch > cat <<EOF >> data/postgresql.conf > archive_mode = on > archive_command = 'cp %p ../arch/%f' > restore_command = 'cp ../arch/%f %p' > EOF > pg_ctl -D data start > pg_basebackup -D sby1 -c fast > cp -a sby1 sby2 > cat <<EOF >> sby1/postgresql.conf > port = 5433 > EOF > touch sby1/standby.signal > pg_ctl -D sby1 start > cat <<EOF >> sby2/postgresql.conf > port = 5434 > primary_conninfo = 'port=5433' > EOF > touch sby2/standby.signal > pg_ctl -D sby2 start > pgbench -i -s2 > pg_ctl -D sby2 restart > -------------------------------------------- > > In this case, after restarting the standby connecting to another > (cascading) standby, I observed the following error. > > FATAL: could not receive data from WAL stream: ERROR: requested > starting point 0/04000000 is ahead of the WAL flush position of this > server 0/03FFE8D0 > > Regards, > > -- > Fujii Masao Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-02-02T02:16:56Z
Hi, On Fri, Jan 30, 2026 at 11:12 AM Fujii Masao <masao.fujii@gmail.com> wrote: > > On Thu, Jan 29, 2026 at 9:22 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > Thanks for your report. I can reliably reproduce the issue on HEAD > > using your scripts. I’ve analyzed the problem and am proposing a patch > > to fix it. > > > > --- Analysis > > When a cascading standby streams from an archive-only upstream: > > > > 1. The upstream's GetStandbyFlushRecPtr() returns only replay position > > (no received-but-not-replayed buffer since there's no walreceiver) > > 2. When streaming ends and the cascade falls back to archive recovery, > > it can restore WAL segments from its own archive access > > 3. The cascade's read position (RecPtr) advances beyond what the > > upstream has replayed > > 4. On reconnect, the cascade requests streaming from RecPtr, which the > > upstream rejects as "ahead of flush position" > > > > --- Proposed Fix > > > > Track the last confirmed flush position from streaming > > (lastStreamedFlush) and clamp the streaming start request when it > > exceeds that position: > > I haven't read the patch yet, but doesn't lastStreamedFlush represent > the same LSN as tliRecPtr or replayLSN (the arguments to > WaitForWALToBecomeAvailable())? If so, we may not need to introduce > a new variable to track this LSN. lastStreamedFlush is the upstream’s confirmed flush point from the last streaming session—what the sender guaranteed it had. tliRecPtr is the LSN of the start of the current WAL record which used to determine which timeline that record belongs to (tliOfPointInHistory), and replayLSN is how far we’ve applied locally. After archive fallback, both tliRecPtr and replayLSN can be ahead of what the upstream has, so they can’t safely cap a reconnect. LastStreamedFlush is used as the upstream-capability bound. > The choice of which LSN is used as the replication start point has varied > over time to handle corner cases (for example, commit 06687198018). > That makes me wonder whether we should first better understand > why WaitForWALToBecomeAvailable() currently uses RecPtr as > the starting point. AFAICS, fix 06687198018 addresses a scenario where a standby gets stuck reading a continuation record that spans multiple pages/segments when the pages must come from different sources. The problem: if the first page is read successfully from local pg_wal but the second page contains garbage from a recycled segment, the old code would enter an infinite loop. This happened because: Late failure detection: Page header validation occurred inside XLogReadRecord(), which triggered ReadRecord()'s retry-from-beginning logic—restarting the entire record read from local sources without ever trying streaming. Wrong streaming start position: Even if streaming was eventually attempted, it started from tliRecPtr (record start) rather than RecPtr (current read position), potentially re-requesting segments the primary had already recycled. The fix has two parts: Early page header validation: Validate the page header immediately after reading, before returning to the caller. If garbage is detected (typically via xlp_pageaddr mismatch), jump directly to next_record_is_invalid to try an alternative source (streaming), bypassing ReadRecord()'s retry loop. Correct streaming start position: Change from ptr = tliRecPtr to ptr = RecPtr, so streaming begins at the position where data is actually needed. The record start position (tliRecPtr) is still used for timeline determination, but no longer for the streaming start LSN. Together, these changes ensure the standby escapes the local-read retry loop and fetches the continuation data from the correct position via streaming. > BTW, with v1 patch, I was able to reproduce the issue using the following steps: > > -------------------------------------------- > initdb -D data > mkdir arch > cat <<EOF >> data/postgresql.conf > archive_mode = on > archive_command = 'cp %p ../arch/%f' > restore_command = 'cp ../arch/%f %p' > EOF > pg_ctl -D data start > pg_basebackup -D sby1 -c fast > cp -a sby1 sby2 > cat <<EOF >> sby1/postgresql.conf > port = 5433 > EOF > touch sby1/standby.signal > pg_ctl -D sby1 start > cat <<EOF >> sby2/postgresql.conf > port = 5434 > primary_conninfo = 'port=5433' > EOF > touch sby2/standby.signal > pg_ctl -D sby2 start > pgbench -i -s2 > pg_ctl -D sby2 restart > -------------------------------------------- > > In this case, after restarting the standby connecting to another > (cascading) standby, I observed the following error. > > FATAL: could not receive data from WAL stream: ERROR: requested > starting point 0/04000000 is ahead of the WAL flush position of this > server 0/03FFE8D0 > After sby2 restarts, its WAL read position (RecPtr) is set to the segment boundary 0/04000000, but the upstream sby1 (archive-only standby with no walreceiver) can only serve up to its replay position 0/03FFE8D0. The cascade requests WAL ahead of what the upstream can provide.The issue is that no in-memory state survives the restart to cap the streaming start request. Before restart, the walreceiver knew what the upstream had confirmed; after restart, that information is lost. One potential solution is a "handshake clamp": after connecting, obtain the upstream's current flush LSN from IDENTIFY_SYSTEM and clamp the streaming start position to Min(startpoint, primaryFlush) before sending START_REPLICATION. But I think this is somewhat complicated. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-16T21:16:49Z
On Thu, Jan 29, 2026 at 11:33 AM Fujii Masao wrote: > Interestingly, I couldn't reproduce it on v11 using the same test case. > This makes me wonder whether the issue was introduced in v12 or later. I did some investigation on this. The bug actually reproduces on v11 with the same setup (archive-only upstream standby + cascading standby with restore_command). I ran the test and got the exact same error: FATAL: could not receive data from WAL stream: ERROR: requested starting point 0/4000000 is ahead of the WAL flush position of this server 0/3FFEED8 The reason Fujii-san might not have seen it on v11 is likely related to how pg_basebackup works with recovery.conf. In the PG12+ reproducer, sby1 has no primary_conninfo (just standby.signal), making it archive-only. But in PG11, when adapting the test, if sby1 retains any primary_conninfo from the basebackup setup, it would stream from the primary and its flush position would stay current, masking the bug. I bisected further and found that the check causing the rejection was introduced by commit abfd192b1b5 ("Allow a streaming replication standby to follow a timeline switch", 2012-12-13, Heikki Linnakangas), which first appeared in PG 9.3. That commit added this validation in StartReplication(): if (am_cascading_walsender) FlushPtr = GetStandbyFlushRecPtr(); else FlushPtr = GetFlushRecPtr(); if (FlushPtr < cmd->startpoint) { ereport(ERROR, errmsg("requested starting point ... is ahead of the WAL flush position ...")); } Before that commit (PG 9.2), the walsender had no such check and would just start sending from whatever position was requested, waiting for the data to become available if needed. So the bug has existed since PG 9.3, not since PG 12. The check itself is correct -- you shouldn't serve WAL you don't have. The real issue is on the requesting side: the cascading standby asks for a position it advanced to via archive recovery, which the upstream hasn't reached yet. Best regards, Marco On Thu, Jan 29, 2026 at 12:33 PM Fujii Masao <masao.fujii@gmail.com> wrote: > On Thu, Jan 29, 2026 at 2:03 AM Marco Nenciarini > <marco.nenciarini@enterprisedb.com> wrote: > > > > Hi hackers, > > > > I've encountered a bug in PostgreSQL's streaming replication where > cascading > > standbys fail to reconnect after falling back to archive recovery. The > issue > > occurs when the upstream standby uses archive-only recovery. > > > > The standby requests streaming from the wrong WAL position (next segment > boundary > > instead of the current position), causing connection failures with this > error: > > > > ERROR: requested starting point 0/A000000 is ahead of the WAL flush > > position of this server 0/9000000 > > Thanks for the report! > I was also able to reproduce this issue on the master branch. > > Interestingly, I couldn't reproduce it on v11 using the same test case. > This makes me wonder whether the issue was introduced in v12 or later. > > Do you see the same behavior in your environment? > > Regards, > > -- > Fujii Masao > -
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-16T21:49:44Z
Attached is a v2 patch that implements the "handshake clamp" approach Xuneng suggested. Rather than tracking lastStreamedFlush in process-local state (which doesn't survive a cascade restart, as Fujii-san demonstrated), it uses the WAL flush position already returned by IDENTIFY_SYSTEM. The walreceiver now checks the upstream's flush position before issuing START_REPLICATION. If the requested startpoint is ahead (on the same timeline), it waits for wal_retrieve_retry_interval and retries. This works across restarts since it queries the upstream's live position on every connection attempt, and requires no new state variables. When timelines differ, we let START_REPLICATION handle the timeline negotiation as before. The patch includes a TAP test (053_cascade_reconnect.pl) that reproduces the scenario and verifies the fix.
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-17T01:04:16Z
Hi, Thanks for the patch. On Tue, Mar 17, 2026 at 5:49 AM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Attached is a v2 patch that implements the "handshake clamp" approach > Xuneng suggested. Rather than tracking lastStreamedFlush in > process-local state (which doesn't survive a cascade restart, as > Fujii-san demonstrated), it uses the WAL flush position already > returned by IDENTIFY_SYSTEM. > > The walreceiver now checks the upstream's flush position before issuing > START_REPLICATION. If the requested startpoint is ahead (on the same > timeline), it waits for wal_retrieve_retry_interval and retries. This > works across restarts since it queries the upstream's live position on > every connection attempt, and requires no new state variables. > > When timelines differ, we let START_REPLICATION handle the timeline > negotiation as before. > > The patch includes a TAP test (053_cascade_reconnect.pl) that > reproduces the scenario and verifies the fix. > I haven’t looked into it in detail yet, but it looks good overall. I’ll test it further and verify that the issue has been resolved. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-17T01:15:22Z
On Tue, Mar 17, 2026 at 9:04 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > Hi, > > Thanks for the patch. > > On Tue, Mar 17, 2026 at 5:49 AM Marco Nenciarini > <marco.nenciarini@enterprisedb.com> wrote: > > > > Attached is a v2 patch that implements the "handshake clamp" approach > > Xuneng suggested. Rather than tracking lastStreamedFlush in > > process-local state (which doesn't survive a cascade restart, as > > Fujii-san demonstrated), it uses the WAL flush position already > > returned by IDENTIFY_SYSTEM. > > > > The walreceiver now checks the upstream's flush position before issuing > > START_REPLICATION. If the requested startpoint is ahead (on the same > > timeline), it waits for wal_retrieve_retry_interval and retries. This > > works across restarts since it queries the upstream's live position on > > every connection attempt, and requires no new state variables. > > > > When timelines differ, we let START_REPLICATION handle the timeline > > negotiation as before. > > > > The patch includes a TAP test (053_cascade_reconnect.pl) that > > reproduces the scenario and verifies the fix. > > > > I haven’t looked into it in detail yet, but it looks good overall. > I’ll test it further and verify that the issue has been resolved. One thing I’m not sure about is whether we need to create a standalone test file for this patch, or if it would fit well within existing TAP tests. I found several places for integration: 001_stream_rep.pl: it already has a primary -> standby -> cascading standby setup, and it even touches primary_conninfo reload behavior. But it is already a large mixed-purpose file, and this bug needs a fairly specific archive-fallback reconnection story. Adding it there would make that file even less focused. 025_stuck_on_old_timeline.pl: this is the nearest thematic neighbor since it combines cascading replication and archive/stream interactions. But it is really about timeline-following after promotion, not “downstream advances via archive and then must reconnect to an upstream that is still behind”. 048_vacuum_horizon_floor.pl: it already exercises stopping and restarting walreceiver via primary_conninfo reload, but it has nothing to do with archive fallback or cascading reconnect logic. The failure scenario is specific enough, and the three-node setup plus archive fallback plus reconnect check seems to be a coherent reproducer on its own. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-17T08:12:50Z
I agree, a standalone test file is the right call here. I looked at the same candidates. 025_stuck_on_old_timeline.pl is the closest thematic match, but its archive command intentionally copies only history files and the whole test revolves around promotion and timeline following. Adapting it would mean replacing the archive command and skipping the promotion, which defeats its original purpose. The reconnect-after-archive-fallback scenario is distinct enough to justify its own file, and at 143 lines it's reasonably small. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-17T09:31:23Z
Since this bug dates back to 9.3, the fix will likely need backpatching. The v2 patch changes the walrcv_identify_system() signature, which would be an ABI break on stable branches (walrcv_identify_system_fn is a function pointer in the WalReceiverFunctionsType struct). Attached is a backpatch-compatible variant that avoids the API change. Instead of adding a parameter, libpqrcv_identify_system() stores the flush position in a new global variable (WalRcvIdentifySystemLsn), and the walreceiver reads it directly. The fix logic and TAP test are otherwise identical. For master I'd still prefer the v2 approach with the extended signature, since it's cleaner and there's no ABI constraint. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-17T11:36:25Z
On Tue, Mar 17, 2026 at 4:13 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > I agree, a standalone test file is the right call here. > > I looked at the same candidates. 025_stuck_on_old_timeline.pl is the > closest thematic match, but its archive command intentionally copies > only history files and the whole test revolves around promotion and > timeline following. Adapting it would mean replacing the archive > command and skipping the promotion, which defeats its original purpose. > > The reconnect-after-archive-fallback scenario is distinct enough to > justify its own file, and at 143 lines it's reasonably small. > > Best regards, > Marco I’ve applied the patch and verified the fix using the two scripts you provided earlier, as well as the failing test from v1 provided by Fujii-san. I’ve also made some small improvements to the TAP test: 1) Added a positive synchronization point using wait_for_event() on walreceiver / WalReceiverUpstreamCatchup, so the test now proves it enters the reconnect-behind-upstream window before asserting outcomes. 2) Replaced broad log scanning with a scoped log window: - capture logfile offset after rotation - use slurp_file(..., $offset) for post-restart assertions only - assert absence of the old “requested starting point … ahead of the WAL flush position” error in that bounded window. Please check it. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-17T11:55:53Z
Thanks for verifying the fix and improving the test, Xuneng. The wait_for_event() synchronization is a nice addition — it gives deterministic proof that the walreceiver actually entered the upstream-catchup path. The scoped log window with slurp_file() is also cleaner than the broad log_contains() I had before. The v3 test improvements look good to me. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-17T12:13:36Z
On Tue, Mar 17, 2026 at 5:31 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Since this bug dates back to 9.3, the fix will likely need backpatching. > The v2 patch changes the walrcv_identify_system() signature, which would > be an ABI break on stable branches (walrcv_identify_system_fn is a > function pointer in the WalReceiverFunctionsType struct). > > Attached is a backpatch-compatible variant that avoids the API change. > Instead of adding a parameter, libpqrcv_identify_system() stores the > flush position in a new global variable (WalRcvIdentifySystemLsn), and > the walreceiver reads it directly. The fix logic and TAP test are > otherwise identical. > > For master I'd still prefer the v2 approach with the extended signature, > since it's cleaner and there's no ABI constraint. > > Best regards, > Marco I think that the ABI concern for backpatching is valid, and the proposed workaround looks reasonable to me. Resetting WalRcvIdentifySystemLsn before walrcv_identify_system() seems like a sensible defensive move, so I’ve added it into v3. The TAP test has also been updated as well. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-17T12:20:42Z
On Tue, Mar 17, 2026 at 7:56 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Thanks for verifying the fix and improving the test, Xuneng. > > The wait_for_event() synchronization is a nice addition — it gives > deterministic proof that the walreceiver actually entered the > upstream-catchup path. The scoped log window with slurp_file() is > also cleaner than the broad log_contains() I had before. > > The v3 test improvements look good to me. > > Best regards, > Marco Thanks for checking. I think we also need to add the new tap test to meson.build for the master patch as well. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-18T01:51:28Z
On Tue, Mar 17, 2026 at 8:20 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > On Tue, Mar 17, 2026 at 7:56 PM Marco Nenciarini > <marco.nenciarini@enterprisedb.com> wrote: > > > > Thanks for verifying the fix and improving the test, Xuneng. > > > > The wait_for_event() synchronization is a nice addition — it gives > > deterministic proof that the walreceiver actually entered the > > upstream-catchup path. The scoped log window with slurp_file() is > > also cleaner than the broad log_contains() I had before. > > After thinking about this more, I’m less satisfied and convinced with polling at wal_retrieve_retry_interval. If the upstream stalls for a long time, or permanently, the walreceiver can loop indefinitely, leaving startup effectively pinned in the streaming path instead of switching to other WAL sources. In that case, repeated “ahead of flush position” log entries can also become noisy. On the other hand, if the upstream catches up quickly, walreceiver still won’t notice until the next interval, adding unnecessary latency of up to one full wal_retrieve_retry_interval. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-18T08:33:47Z
On Wed, Mar 18, 2026 at 2:51 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > On Tue, Mar 17, 2026 at 8:20 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > > > On Tue, Mar 17, 2026 at 7:56 PM Marco Nenciarini > > <marco.nenciarini@enterprisedb.com> wrote: > > > > > > Thanks for verifying the fix and improving the test, Xuneng. > > > > > > The wait_for_event() synchronization is a nice addition — it gives > > > deterministic proof that the walreceiver actually entered the > > > upstream-catchup path. The scoped log window with slurp_file() is > > > also cleaner than the broad log_contains() I had before. > > > > > After thinking about this more, I’m less satisfied and convinced with > polling at wal_retrieve_retry_interval. If the upstream stalls for a > long time, or permanently, the walreceiver can loop indefinitely, > leaving startup effectively pinned in the streaming path instead of > switching to other WAL sources. In that case, repeated “ahead of flush > position” log entries can also become noisy. On the other hand, if the > upstream catches up quickly, walreceiver still won’t notice until the > next interval, adding unnecessary latency of up to one full > wal_retrieve_retry_interval. > Good points, Xuneng. For the log noise: we could emit the first "ahead of flush position" message at LOG level, then demote subsequent attempts to DEBUG1 until the condition clears. That keeps the initial occurrence visible for diagnostics without flooding the log during a long wait. For the indefinite loop: I agree that unbounded polling is not ideal. The gap this fix targets is bounded in practice: the startup process alternates between archive recovery and streaming attempts, so at each streaming attempt the cascade is at most one WAL segment ahead of the upstream. If the gap is larger than that, something more fundamental is wrong and the walreceiver should get out of the way so the startup process can fall back to other WAL sources. We could cap the wait with a threshold: if startpoint is more than one wal_segment_size ahead of the upstream's flush position, skip the wait and let START_REPLICATION proceed normally (and fail), so the walreceiver exits and the startup process can switch to archive. That way we absorb the one-segment gap that arises naturally from archive recovery, without masking larger problems. Thoughts on whether wal_segment_size is the right bound, or if something else would be more appropriate? Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-18T09:49:25Z
Here are the v4 patches implementing what I described above. On top of Xuneng's v3 (keeping the wait_for_event and scoped log window test improvements), the main changes are: - The wait is now capped at one wal_segment_size. If the gap is larger, we skip the wait and let START_REPLICATION fail normally so the startup process can fall back to archive. This avoids indefinite polling when the upstream is fundamentally behind. - The first "ahead of flush position" message is logged at LOG, subsequent ones at DEBUG1, to cut down on noise during a long wait. Two patches attached: v4-0001 for master (extends the walrcv_identify_system API with an optional server_lsn output parameter) and v4-backpatch-0001 for stable branches (uses a global variable to preserve ABI, per Alvaro's suggestion). Both pass the new TAP test. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-19T09:33:16Z
Hi Marco, On Wed, Mar 18, 2026 at 4:34 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > On Wed, Mar 18, 2026 at 2:51 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: >> >> On Tue, Mar 17, 2026 at 8:20 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: >> > >> > On Tue, Mar 17, 2026 at 7:56 PM Marco Nenciarini >> > <marco.nenciarini@enterprisedb.com> wrote: >> > > >> > > Thanks for verifying the fix and improving the test, Xuneng. >> > > >> > > The wait_for_event() synchronization is a nice addition — it gives >> > > deterministic proof that the walreceiver actually entered the >> > > upstream-catchup path. The scoped log window with slurp_file() is >> > > also cleaner than the broad log_contains() I had before. >> > > >> >> After thinking about this more, I’m less satisfied and convinced with >> polling at wal_retrieve_retry_interval. If the upstream stalls for a >> long time, or permanently, the walreceiver can loop indefinitely, >> leaving startup effectively pinned in the streaming path instead of >> switching to other WAL sources. In that case, repeated “ahead of flush >> position” log entries can also become noisy. On the other hand, if the >> upstream catches up quickly, walreceiver still won’t notice until the >> next interval, adding unnecessary latency of up to one full >> wal_retrieve_retry_interval. > > > Good points, Xuneng. > > For the log noise: we could emit the first "ahead of flush position" > message at LOG level, then demote subsequent attempts to DEBUG1 until > the condition clears. That keeps the initial occurrence visible for > diagnostics without flooding the log during a long wait. > > For the indefinite loop: I agree that unbounded polling is not ideal. > The gap this fix targets is bounded in practice: the startup process > alternates between archive recovery and streaming attempts, so at > each streaming attempt the cascade is at most one WAL segment ahead > of the upstream. If the gap is larger than that, something more > fundamental is wrong and the walreceiver should get out of the way > so the startup process can fall back to other WAL sources. I am not sure about this bound here. It seems to me that the gap could be several segments due to the upstream lag. With this assumption, It also seems not very ideal to just clamp the ptr to the flush lsn of the upstream server and proceed the handshake since the potential duplication of segments could be large. > We could cap the wait with a threshold: if startpoint is more than > one wal_segment_size ahead of the upstream's flush position, skip the > wait and let START_REPLICATION proceed normally (and fail), so the > walreceiver exits and the startup process can switch to archive. > That way we absorb the one-segment gap that arises naturally from > archive recovery, without masking larger problems. > > Thoughts on whether wal_segment_size is the right bound, or if > something else would be more appropriate? Even with only a one-segment gap, if the upstream server’s flush LSN does not advance, we would remain stuck polling indefinitely. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-19T09:59:38Z
Hi Xuneng, On Wed, Mar 19, 2026 at 10:33 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > I am not sure about this bound here. It seems to me that the gap could > be several segments due to the upstream lag. The gap is bounded to one segment, but let me explain why more clearly because I don't think I did a good job of it before. The mismatch exists even when both nodes have replayed exactly the same WAL. The upstream standby never produces WAL itself - it only advances via archive (or streaming, but that's exactly what we're trying to establish here). After both have replayed the same archived file, the cascade's "next position to read" ends up just past the upstream's "last position replayed" (which is what GetStandbyFlushRecPtr reports). That gap is inherently within one segment. This is the core of the bug: the gap never closes on its own. When the next WAL file arrives, both nodes restore it and advance by one full segment, but the same mismatch reappears. They keep advancing and never successfully start streaming. When the upstream is genuinely far behind (gap larger than one segment), the threshold correctly lets START_REPLICATION fail so the startup process can fall back to archive. > Even with only a one-segment gap, if the upstream server's flush LSN > does not advance, we would remain stuck polling indefinitely. True, but this is consistent with how the walreceiver already behaves when it is connected and streaming: if the upstream stops producing WAL, the walreceiver just sits there waiting on the connection indefinitely. So the polling behavior here is no worse than what already happens in normal operation. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-20T00:52:46Z
On Thu, Mar 19, 2026 at 5:59 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Hi Xuneng, > > On Wed, Mar 19, 2026 at 10:33 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > > > I am not sure about this bound here. It seems to me that the gap could > > be several segments due to the upstream lag. > > The gap is bounded to one segment, but let me explain why more > clearly because I don't think I did a good job of it before. > > The mismatch exists even when both nodes have replayed exactly the > same WAL. The upstream standby never produces WAL itself - it only > advances via archive (or streaming, but that's exactly what we're > trying to establish here). After both have replayed the same > archived file, the cascade's "next position to read" ends up just > past the upstream's "last position replayed" (which is what > GetStandbyFlushRecPtr reports). That gap is inherently within one > segment. > > This is the core of the bug: the gap never closes on its own. When > the next WAL file arrives, both nodes restore it and advance by one > full segment, but the same mismatch reappears. They keep advancing > and never successfully start streaming. > > When the upstream is genuinely far behind (gap larger than one > segment), the threshold correctly lets START_REPLICATION fail so the > startup process can fall back to archive. The one-segment bound holds for the case where both nodes have replayed exactly the same WAL — the gap comes from RequestXLogStreaming truncating recptr to the segment boundary, so startpoint is always at the start of the next segment while GetStandbyFlushRecPtr returns replayPtr within the current one. I think that part of the analysis is correct. But the gap can legitimately be multiple segments. Consider: the upstream standby goes down (or is restarted for maintenance) while the primary keeps generating and archiving WAL. The cascade's walreceiver loses its connection, startup falls back to archive recovery, and restores as many segments as the archive can supply. Meanwhile the upstream's replayPtr is frozen at wherever it was when it went down. When the upstream comes back and the cascade tries to reconnect, the gap can be many segments — bounded only by how much WAL the primary archived while the upstream was down. This is a normal operational scenario (the reproducer_restart_upstream_portable.sh script exercises this), not "something fundamentally wrong." The question is whether we should fail in this case. As you mentioned, if this behavior is intentional, that’s fine. Otherwise, it could lead to problems. If there's a consensus for this and the fix of one-segment gap, the current tap test would become non-deterministic. The test controls the amount of WAL generated (1000 rows of integers — a few hundred KB at most), which with the default 16MB wal_segment_size almost certainly fits within one segment. And pg_switch_wal() ensures one segment boundary is created and archived. So in practice, one new segment ends up in the archive that standby_a doesn't have. But there's no explicit mechanism to guarantee this. Several things could cause more WAL than expected: Checkpoints running between step 1 (catchup) and step 4 (INSERT) could push the WAL position across a segment boundary. Background WAL activity (stats, etc.) adds volume. If the initial data (1000 rows from CREATE TABLE ... generate_series) happened to leave the WAL position near the end of a segment, the new INSERT could spill into the next segment. The test also doesn't verify the gap size — it never checks what startpoint or primaryFlushPtr actually are. It only checks the outcome: the wait event is hit (WalReceiverUpstreamCatchup) and no "ahead of flush position" errors appear in the log. These assertions would pass regardless of whether the gap is one segment or five. So the test works because the workload is small enough to make a one-segment gap the most likely outcome, but it's not guaranteed. If a threshold at wal_segment_size were added to the patch, there would be no test coverage for the multi-segment case — and no guarantee the existing test wouldn't accidentally exercise it on systems with smaller wal_segment_size or heavier background WAL activity. > > Even with only a one-segment gap, if the upstream server's flush LSN > > does not advance, we would remain stuck polling indefinitely. > > True, but this is consistent with how the walreceiver already behaves > when it is connected and streaming: if the upstream stops producing > WAL, the walreceiver just sits there waiting on the connection > indefinitely. So the polling behavior here is no worse than what > already happens in normal operation. I think the difference is that -- during normal streaming, wal_receiver_timeout will eventually fire and kill the connection, whereas the catch-up polling loop has no such timeout. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-20T03:33:53Z
On Fri, Mar 20, 2026 at 8:52 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > On Thu, Mar 19, 2026 at 5:59 PM Marco Nenciarini > <marco.nenciarini@enterprisedb.com> wrote: > > > > Hi Xuneng, > > > > On Wed, Mar 19, 2026 at 10:33 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > > > > > I am not sure about this bound here. It seems to me that the gap could > > > be several segments due to the upstream lag. > > > > The gap is bounded to one segment, but let me explain why more > > clearly because I don't think I did a good job of it before. > > > > The mismatch exists even when both nodes have replayed exactly the > > same WAL. The upstream standby never produces WAL itself - it only > > advances via archive (or streaming, but that's exactly what we're > > trying to establish here). After both have replayed the same > > archived file, the cascade's "next position to read" ends up just > > past the upstream's "last position replayed" (which is what > > GetStandbyFlushRecPtr reports). That gap is inherently within one > > segment. > > > > This is the core of the bug: the gap never closes on its own. When > > the next WAL file arrives, both nodes restore it and advance by one > > full segment, but the same mismatch reappears. They keep advancing > > and never successfully start streaming. > > > > When the upstream is genuinely far behind (gap larger than one > > segment), the threshold correctly lets START_REPLICATION fail so the > > startup process can fall back to archive. > > The one-segment bound holds for the case where both nodes have > replayed exactly the same WAL — the gap comes from > RequestXLogStreaming truncating recptr to the segment boundary, so > startpoint is always at the start of the next segment while > GetStandbyFlushRecPtr returns replayPtr within the current one. I > think that part of the analysis is correct. >> After both have replayed the same > > archived file, the cascade's "next position to read" ends up just > > past the upstream's "last position replayed" (which is what > > GetStandbyFlushRecPtr reports). After taking a closer look, I’m less certain about this. I’ll investigate further. Could you also explain why you think this is the case? > But the gap can legitimately be multiple segments. Consider: the > upstream standby goes down (or is restarted for maintenance) while the > primary keeps generating and archiving WAL. The cascade's walreceiver > loses its connection, startup falls back to archive recovery, and > restores as many segments as the archive can supply. Meanwhile the > upstream's replayPtr is frozen at wherever it was when it went down. > > When the upstream comes back and the cascade tries to reconnect, the > gap can be many segments — bounded only by how much WAL the primary > archived while the upstream was down. This is a normal operational > scenario (the reproducer_restart_upstream_portable.sh script exercises > this), not "something fundamentally wrong." > > The question is whether we should fail in this case. As you mentioned, > if this behavior is intentional, that’s fine. Otherwise, it could lead > to problems. > > If there's a consensus for this and the fix of one-segment gap, the > current tap test would become non-deterministic. > > The test controls the amount of WAL generated (1000 rows of integers — > a few hundred KB at most), which with the default 16MB > wal_segment_size almost certainly fits within one segment. And > pg_switch_wal() ensures one segment boundary is created and archived. > So in practice, one new segment ends up in the archive that standby_a > doesn't have. > > But there's no explicit mechanism to guarantee this. Several things > could cause more WAL than expected: > > Checkpoints running between step 1 (catchup) and step 4 (INSERT) could > push the WAL position across a segment boundary. > Background WAL activity (stats, etc.) adds volume. > If the initial data (1000 rows from CREATE TABLE ... generate_series) > happened to leave the WAL position near the end of a segment, the new > INSERT could spill into the next segment. > > The test also doesn't verify the gap size — it never checks what > startpoint or primaryFlushPtr actually are. It only checks the > outcome: the wait event is hit (WalReceiverUpstreamCatchup) and no > "ahead of flush position" errors appear in the log. These assertions > would pass regardless of whether the gap is one segment or five. > > So the test works because the workload is small enough to make a > one-segment gap the most likely outcome, but it's not guaranteed. If a > threshold at wal_segment_size were added to the patch, there would be > no test coverage for the multi-segment case — and no guarantee the > existing test wouldn't accidentally exercise it on systems with > smaller wal_segment_size or heavier background WAL activity. > > > > > Even with only a one-segment gap, if the upstream server's flush LSN > > > does not advance, we would remain stuck polling indefinitely. > > > > True, but this is consistent with how the walreceiver already behaves > > when it is connected and streaming: if the upstream stops producing > > WAL, the walreceiver just sits there waiting on the connection > > indefinitely. So the polling behavior here is no worse than what > > already happens in normal operation. > > I think the difference is that -- during normal streaming, > wal_receiver_timeout will eventually fire and kill the connection, > whereas the catch-up polling loop has no such timeout. > > -- > Best, > Xuneng -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-20T11:45:02Z
Hi Xuneng, On Fri, Mar 20, 2026 at 1:52 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > The one-segment bound holds for the case where both nodes have > replayed exactly the same WAL -- the gap comes from > RequestXLogStreaming truncating recptr to the segment boundary, so > startpoint is always at the start of the next segment while > GetStandbyFlushRecPtr returns replayPtr within the current one. I > think that part of the analysis is correct. Right, I verified it: RequestXLogStreaming (walreceiverfuncs.c) explicitly truncates recptr to the segment start to avoid creating broken partial segments. So the cascade always asks to stream from the beginning of a segment, while GetStandbyFlushRecPtr on the upstream returns replayPtr somewhere inside that same segment. The gap is the distance from the segment start to replayPtr, always less than wal_segment_size. > But the gap can legitimately be multiple segments. Consider: the > upstream standby goes down (or is restarted for maintenance) while the > primary keeps generating and archiving WAL. Agreed, that scenario produces a multi-segment gap. But I think handling it is out of scope for this patch. The bug we're fixing is that a cascade can never start streaming from an upstream that is fully caught up, because of the RequestXLogStreaming truncation. Making the walreceiver wait for an upstream that is genuinely many segments behind would be a feature, not a bug fix, and it would need its own discussion about the right behavior. The wal_segment_size threshold keeps the fix narrowly targeted at this specific bug: absorb the sub-segment gap that arises from the truncation, let everything else fail as before. > If there's a consensus for this and the fix of one-segment gap, the > current tap test would become non-deterministic. Good catch. I'll tighten the test to make sure the gap stays within one segment. > I think the difference is that -- during normal streaming, > wal_receiver_timeout will eventually fire and kill the connection, > whereas the catch-up polling loop has no such timeout. Fair point. I'll add a wal_receiver_timeout check to the polling loop so the walreceiver exits if it has been waiting too long, same as it would during normal streaming. Updated patches attached. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-20T12:40:42Z
On Fri, Mar 20, 2026 at 4:33 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > After taking a closer look, I'm less certain about this. I'll > investigate further. Could you also explain why you think this is the > case? The mechanism is in RequestXLogStreaming (walreceiverfuncs.c, around line 276): it explicitly truncates recptr to the segment start before passing it to the walreceiver. So even when both nodes have replayed the same records, the cascade's startpoint lands at the beginning of the next segment while the upstream's GetStandbyFlushRecPtr returns replayPtr somewhere inside the current one. I covered this in more detail in my reply to your previous message. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-03-21T10:07:16Z
On Fri, Mar 20, 2026 at 8:40 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > On Fri, Mar 20, 2026 at 4:33 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > > > After taking a closer look, I'm less certain about this. I'll > > investigate further. Could you also explain why you think this is the > > case? > > The mechanism is in RequestXLogStreaming (walreceiverfuncs.c, around > line 276): it explicitly truncates recptr to the segment start before > passing it to the walreceiver. So even when both nodes have replayed > the same records, the cascade's startpoint lands at the beginning of > the next segment while the upstream's GetStandbyFlushRecPtr returns > replayPtr somewhere inside the current one. > > I covered this in more detail in my reply to your previous message. > > Best regards, > Marco > if (XLogSegmentOffset(recptr, wal_segment_size) != 0) recptr -= XLogSegmentOffset(recptr, wal_segment_size); I think this rounds down to the start of segment that contains targetPagePtr + reqLen. It does not round up. So if both standby have replayed the same record, the cascade's startpoint would land at the beginning of the current segment, which will provide a legitimate LSN to upstream server. This case would be fine. Am I missing something here? -- Best, Xuneng -
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-21T10:37:40Z
Hi Xuneng, On Fri, Mar 21, 2026 at 10:07 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > I think this rounds down to the start of segment that contains > targetPagePtr + reqLen. It does not round up. So if both standby have > replayed the same record, the cascade's startpoint would land at the > beginning of the current segment, which will provide a legitimate LSN > to upstream server. This case would be fine. Am I missing something > here? You're right that it rounds down, but the value being rounded is not the replay position. Let me trace the exact path: 1. XLogPageRead calls WaitForWALToBecomeAvailable(targetPagePtr + reqLen). After archive recovery finishes reading segment N, the next page request is for the first page of segment N+1, so this value is already in segment N+1. 2. WaitForWALToBecomeAvailable sets ptr = RecPtr (line 3843) and passes it to RequestXLogStreaming. 3. RequestXLogStreaming truncates to the segment boundary, but since the value is already at (or just past) the start of segment N+1, it stays at the start of N+1. 4. On the upstream side, GetStandbyFlushRecPtr returns replayPtr, which is the end of the last replayed record inside segment N. 5. Start of N+1 > replayPtr in N => "ahead of the WAL flush position". So the gap doesn't come from the truncation itself -- it comes from archive recovery processing whole segment files. After both nodes replay the same archived segment N, the cascade's next read position is already in N+1 while the upstream reports a position inside N. The truncation determines the exact startpoint (segment boundary) but the "ahead" condition exists regardless of it. I'll update the code comment in the patch to describe this more precisely. Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-03-21T10:52:28Z
Here are the v6 patches. Xuneng correctly pointed out that RequestXLogStreaming rounds down, not up, so it isn't the cause of the gap. The actual mechanism is that archive recovery processes whole segment files: after both nodes replay the same archived segment N, the cascade's next read position lands at the start of segment N+1, while the upstream's GetStandbyFlushRecPtr returns replayPtr inside segment N. Changes from v5: - Updated the code comment and commit message to describe the correct root cause (archive recovery segment granularity, not RequestXLogStreaming truncation). - Reset the catchup state when the upstream is no longer behind. Without this, if the walreceiver successfully streams, the connection breaks, and it loops back to find itself ahead again, the stale deadline from the previous wait would cause an immediate timeout. Two patches attached: v6-0001 for master (extends the walrcv_identify_system API) and v6-backpatch-0001 for stable branches (global variable to preserve ABI). Best regards, Marco
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-04-27T16:00:45Z
Registered in PG20-1: https://commitfest.postgresql.org/patch/6716/ On Sat, Mar 21, 2026 at 11:52 AM Marco Nenciarini < marco.nenciarini@enterprisedb.com> wrote: > Here are the v6 patches. > > Xuneng correctly pointed out that RequestXLogStreaming rounds down, > not up, so it isn't the cause of the gap. The actual mechanism is > that archive recovery processes whole segment files: after both nodes > replay the same archived segment N, the cascade's next read position > lands at the start of segment N+1, while the upstream's > GetStandbyFlushRecPtr returns replayPtr inside segment N. > > Changes from v5: > > - Updated the code comment and commit message to describe the correct > root cause (archive recovery segment granularity, not > RequestXLogStreaming truncation). > > - Reset the catchup state when the upstream is no longer behind. > Without this, if the walreceiver successfully streams, the > connection breaks, and it loops back to find itself ahead again, > the stale deadline from the previous wait would cause an immediate > timeout. > > Two patches attached: v6-0001 for master (extends the > walrcv_identify_system API) and v6-backpatch-0001 for stable branches > (global variable to preserve ABI). > > Best regards, > Marco > >
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-04-27T16:50:28Z
v7 patches attached. No code changes from v6, just rebased on current master to remove minor offset, and the backpatch file is renamed with a "nocfbot-" prefix so the commitfest bot picks up only the master patch. On Mon, Apr 27, 2026 at 6:00 PM Marco Nenciarini < marco.nenciarini@enterprisedb.com> wrote: > Registered in PG20-1: https://commitfest.postgresql.org/patch/6716/ > > On Sat, Mar 21, 2026 at 11:52 AM Marco Nenciarini < > marco.nenciarini@enterprisedb.com> wrote: > >> Here are the v6 patches. >> >> Xuneng correctly pointed out that RequestXLogStreaming rounds down, >> not up, so it isn't the cause of the gap. The actual mechanism is >> that archive recovery processes whole segment files: after both nodes >> replay the same archived segment N, the cascade's next read position >> lands at the start of segment N+1, while the upstream's >> GetStandbyFlushRecPtr returns replayPtr inside segment N. >> >> Changes from v5: >> >> - Updated the code comment and commit message to describe the correct >> root cause (archive recovery segment granularity, not >> RequestXLogStreaming truncation). >> >> - Reset the catchup state when the upstream is no longer behind. >> Without this, if the walreceiver successfully streams, the >> connection breaks, and it loops back to find itself ahead again, >> the stale deadline from the previous wait would cause an immediate >> timeout. >> >> Two patches attached: v6-0001 for master (extends the >> walrcv_identify_system API) and v6-backpatch-0001 for stable branches >> (global variable to preserve ABI). >> >> Best regards, >> Marco >> >>
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-05-01T02:57:08Z
Hi Marco, On Tue, Apr 28, 2026 at 12:50 AM Marco Nenciarini < marco.nenciarini@enterprisedb.com> wrote: > v7 patches attached. No code changes from v6, just rebased on > current master to remove minor offset, and the backpatch file is > renamed with a "nocfbot-" prefix so the commitfest bot picks up > only the master patch. > > > On Mon, Apr 27, 2026 at 6:00 PM Marco Nenciarini < > marco.nenciarini@enterprisedb.com> wrote: > >> Registered in PG20-1: https://commitfest.postgresql.org/patch/6716/ >> >> On Sat, Mar 21, 2026 at 11:52 AM Marco Nenciarini < >> marco.nenciarini@enterprisedb.com> wrote: >> >>> Here are the v6 patches. >>> >>> Xuneng correctly pointed out that RequestXLogStreaming rounds down, >>> not up, so it isn't the cause of the gap. The actual mechanism is >>> that archive recovery processes whole segment files: after both nodes >>> replay the same archived segment N, the cascade's next read position >>> lands at the start of segment N+1, while the upstream's >>> GetStandbyFlushRecPtr returns replayPtr inside segment N. >>> >>> Changes from v5: >>> >>> - Updated the code comment and commit message to describe the correct >>> root cause (archive recovery segment granularity, not >>> RequestXLogStreaming truncation). >>> >>> - Reset the catchup state when the upstream is no longer behind. >>> Without this, if the walreceiver successfully streams, the >>> connection breaks, and it loops back to find itself ahead again, >>> the stale deadline from the previous wait would cause an immediate >>> timeout. >>> >>> Two patches attached: v6-0001 for master (extends the >>> walrcv_identify_system API) and v6-backpatch-0001 for stable branches >>> (global variable to preserve ABI). >>> >> Polling at intervals stil seems not good to me. But I don't have a better idea for now. -- Best, Xuneng
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Marco Nenciarini <marco.nenciarini@enterprisedb.com> — 2026-05-06T11:27:48Z
Hi Xuneng, You're right that polling isn't ideal. For a backpatchable bug fix though, the trade-off seems reasonable: the change is contained in the walreceiver, doesn't touch the wire protocol, and applies to all back branches. Exploring better designs would be worthwhile but probably belongs in a separate effort. Best regards, Marco On Fri, May 1, 2026 at 4:57 AM Xuneng Zhou <xunengzhou@gmail.com> wrote: > Hi Marco, > > On Tue, Apr 28, 2026 at 12:50 AM Marco Nenciarini < > marco.nenciarini@enterprisedb.com> wrote: > >> v7 patches attached. No code changes from v6, just rebased on >> current master to remove minor offset, and the backpatch file is >> renamed with a "nocfbot-" prefix so the commitfest bot picks up >> only the master patch. >> >> >> On Mon, Apr 27, 2026 at 6:00 PM Marco Nenciarini < >> marco.nenciarini@enterprisedb.com> wrote: >> >>> Registered in PG20-1: https://commitfest.postgresql.org/patch/6716/ >>> >>> On Sat, Mar 21, 2026 at 11:52 AM Marco Nenciarini < >>> marco.nenciarini@enterprisedb.com> wrote: >>> >>>> Here are the v6 patches. >>>> >>>> Xuneng correctly pointed out that RequestXLogStreaming rounds down, >>>> not up, so it isn't the cause of the gap. The actual mechanism is >>>> that archive recovery processes whole segment files: after both nodes >>>> replay the same archived segment N, the cascade's next read position >>>> lands at the start of segment N+1, while the upstream's >>>> GetStandbyFlushRecPtr returns replayPtr inside segment N. >>>> >>>> Changes from v5: >>>> >>>> - Updated the code comment and commit message to describe the correct >>>> root cause (archive recovery segment granularity, not >>>> RequestXLogStreaming truncation). >>>> >>>> - Reset the catchup state when the upstream is no longer behind. >>>> Without this, if the walreceiver successfully streams, the >>>> connection breaks, and it loops back to find itself ahead again, >>>> the stale deadline from the previous wait would cause an immediate >>>> timeout. >>>> >>>> Two patches attached: v6-0001 for master (extends the >>>> walrcv_identify_system API) and v6-backpatch-0001 for stable branches >>>> (global variable to preserve ABI). >>>> >>> > Polling at intervals stil seems not good to me. But I don't have a better > idea for now. > > -- > Best, > Xuneng >
-
Re: BUG: Cascading standby fails to reconnect after falling back to archive recovery
Xuneng Zhou <xunengzhou@gmail.com> — 2026-05-16T07:13:03Z
On Wed, May 6, 2026 at 7:28 PM Marco Nenciarini <marco.nenciarini@enterprisedb.com> wrote: > > Hi Xuneng, > > You're right that polling isn't ideal. For a backpatchable bug fix > though, the trade-off seems reasonable: the change is contained in > the walreceiver, doesn't touch the wire protocol, and applies to all > back branches. Exploring better designs would be worthwhile but > probably belongs in a separate effort. Yeah, it's still better than remaining unfixed. I'll look into it. -- Regards, Xuneng Zhou HighGo Software Co., Ltd.