Thread
-
[BUG] Take a long time to reach consistent after pg_rewind
cca5507 <cca5507@qq.com> — 2026-04-10T09:57:39Z
Hi, Steps to reproduce (PG19): 1) start two nodes, node1 (primary), node2 (standby), both with the following configuration: ``` archive_mode = on archive_command = '/bin/true' archive_timeout = 10 checkpoint_timeout = '60min' wal_keep_size = 1024 logging_collector = on ``` 2) promote node2 3) stop node1 4) make sure the pg_current_wal_insert_lsn() of node2 is at the begin of a wal segment (end with 000028), if not, do a checkpoint and recheck. (archive_timeout will switch the wal) 5) execute pg_rewind with node1 6) start node1 7) now node1 can't reach consistent until node2 write some wal Logs of node1: ``` 2026-04-10 16:16:07.802 CST [45623] LOG: starting backup recovery with redo LSN 0/02000028, checkpoint LSN 0/02000088, on timeline ID 1 2026-04-10 16:16:07.802 CST [45623] LOG: entering standby mode 2026-04-10 16:16:07.803 CST [45623] LOG: redo starts at 0/02000028 2026-04-10 16:16:07.803 CST [45623] LOG: completed backup recovery with redo LSN 0/02000028 and end LSN 0/02000130 2026-04-10 16:16:07.806 CST [45624] LOG: started streaming WAL from primary at 0/04000000 on timeline 2 2026-04-10 16:19:13.083 CST [47039] FATAL: the database system is not yet accepting connections 2026-04-10 16:19:13.083 CST [47039] DETAIL: Consistent recovery state has not been yet reached. 2026-04-10 16:20:16.413 CST [45623] LOG: consistent recovery state reached at 0/04000048 2026-04-10 16:20:16.413 CST [45616] LOG: database system is ready to accept read-only connections ``` Root cause: The min recovery point of node1 is at 0/04000028, but node2 doesn't have any wal after that and may keep idle for a long time. Possible fix: The pg_rewind use pg_current_wal_insert_lsn() to set the min recovery point, which calls GetXLogInsertRecPtr() and returns the latest wal insert pointer. Maybe we should use GetXLogInsertEndRecPtr() which returns the latest wal record end pointer. Thoughts? -- Regards, ChangAo Chen
-
Re: [BUG] Take a long time to reach consistent after pg_rewind
cca5507 <cca5507@qq.com> — 2026-04-13T13:14:19Z
> Possible fix: > > The pg_rewind use pg_current_wal_insert_lsn() to set the min recovery point, which calls > GetXLogInsertRecPtr() and returns the latest wal insert pointer. Maybe we should use > GetXLogInsertEndRecPtr() which returns the latest wal record end pointer. > > Thoughts? Another solution: If minRecoveryPoint is just after a xlog page header, we can move it to the begin of the page. It's safe because we just skip the xlog page header. Do I miss something? Attach a patch done like this. -- Regards, ChangAo Chen
-
Re: [BUG] Take a long time to reach consistent after pg_rewind
surya poondla <suryapoondla4@gmail.com> — 2026-05-19T21:20:56Z
Subject: Re: [BUG] Take a long time to reach consistent after pg_rewind Hi ChangAo, Thanks for the careful diagnosis, I reproduced the hang on macOS on the latest postgres code (It took a lot of iterations to reproduce it) The LSN trace matches your description and I saw the below: minRecoveryPoint = 0/08000028 consistent recovery state reached at = 0/08000060 In my run the standby was stuck for ~9 s; consistency was eventually declared at 0/08000060 because a small upstream record (most likely a RUNNING_XACTS snapshot from bgwriter) landed at 0/08000028 and let lastReplayedEndRecPtr leap past the bad finish line. With the new primary stopped after pg_rewind, the wait was unbounded as expected. Regarding the fix: the underlying issue is that minRecoveryPoint is implicitly expected to be the end-LSN of a real WAL record, because lastReplayedEndRecPtr (the value it gets compared against) can only ever take such values. All current writers respect this expectation except pg_rewind: pg_basebackup uses the backup-end record's EndRecPtr, and the in-running UpdateMinRecoveryPoint path uses buffer LSNs, both of which are record-end LSNs by construction. pg_rewind alone uses pg_current_wal_insert_lsn(), which can return a position just past a page header when the source is idle. That's why I'd lean toward fixing the producer (pg_rewind). Concretely, your original suggestion having pg_rewind use GetXLogInsertEndRecPtr() instead of GetXLogInsertRecPtr(), restores the invariant globally, and doesn't require future call sites that compare against minRecoveryPoint to know about page-header adjustments. If we still want a defense-in-depth guard in CheckRecoveryConsistency() to handle older pg_rewind binaries running against a newer server, the v1 patch is on the right track, but I'd suggest: - documenting in the helper comment why exactly SizeOfXLogShortPHD / SizeOfXLogLongPHD past a page boundary are the only legal "non-record-end" minRecoveryPoint values (i.e. who can produce them and under what conditions); - auditing the other call sites that compare against minRecoveryPoint to confirm none of them needs the same adjustment, with a comment recording the conclusion. I can put together a TAP test under src/bin/pg_rewind/t/ that forces a WAL switch on the source, runs pg_rewind against an otherwise-idle primary, and asserts that the rewound node reaches consistency without further upstream activity. Happy to send a v2 with that test if useful. This is a liveness bug with potentially unbounded wait on idle promoted primaries, so it does seem worth back-patching. Regards, Surya Poondla -
Re: [BUG] Take a long time to reach consistent after pg_rewind
cca5507 <cca5507@qq.com> — 2026-06-11T03:03:17Z
Hi Surya, Thanks for the review and sorry for the late reply. I get a new idea to fix this bug from: https://www.postgresql.org/message-id/05DEB465-3C99-417E-B7FA-275A28068D90%40yandex-team.ru Please see the v2 patch for details. -- Regards, ChangAo Chen
-
Re: [BUG] Take a long time to reach consistent after pg_rewind
Kyotaro Horiguchi <horikyota.ntt@gmail.com> — 2026-06-11T06:35:32Z
Hello. At Thu, 11 Jun 2026 11:03:17 +0800, "cca5507" <cca5507@qq.com> wrote in > I get a new idea to fix this bug from: > > https://www.postgresql.org/message-id/05DEB465-3C99-417E-B7FA-275A28068D90%40yandex-team.ru > > Please see the v2 patch for details. Thanks for the patch. However, I am not sure that using the flush LSN is the right direction. As I understand it, the root of the problem is that get_current_wal_insert_lsn() returns the next WAL insertion position, while minRecoveryPoint is expected to point just past the end of the last required record. At positions immediately following a WAL page or segment header, those locations can be logically equivalent but numerically different. I think pg_rewind is probably using the insert LSN because it wants to choose a conservative position as far ahead as possible. It might be possible to use the flush LSN if the copying logic is carefully arranged, but I would prefer to keep using the insert LSN if we can. I also think it is unfortunate to require client-side tools to be aware of the exact "end of last record + 1" representation. If both representations are logically equivalent at those positions, I would rather have the recovery side accept and normalize them than require every tool to produce exactly the expected form. So I wonder whether we should instead normalize minRecoveryPoint when reading it from the control file, so that these equivalent representations are treated as the same position. That would also make the recovery side a bit more robust. For example, something like this: diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index 73b78a83fa7..660c681795d 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -955,6 +955,20 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr, { minRecoveryPoint = ControlFile->minRecoveryPoint; minRecoveryPointTLI = ControlFile->minRecoveryPointTLI; + + /* + * minRecoveryPoint in the control file is expected to point to the + * location immediately following the end of the target + * record. However, some tools may record a location immediately after + * a WAL segment or page header when the target position is at the + * beginning of a segment or page. Normalize such values here before + * further processing. + */ + if (XLogSegmentOffset(minRecoveryPoint, wal_segment_size) + == SizeOfXLogLongPHD) + minRecoveryPoint -= SizeOfXLogLongPHD; + else if (minRecoveryPoint % XLOG_BLCKSZ == SizeOfXLogShortPHD) + minRecoveryPoint -= SizeOfXLogShortPHD; } else { Thoughts? -- Kyotaro Horiguchi NTT Open Source Software Center -
Re: [BUG] Take a long time to reach consistent after pg_rewind
cca5507 <cca5507@qq.com> — 2026-06-11T07:50:19Z
Hi, > As I understand it, the root of the problem is that > get_current_wal_insert_lsn() returns the next WAL insertion position, > while minRecoveryPoint is expected to point just past the end of the > last required record. At positions immediately following a WAL page or > segment header, those locations can be logically equivalent but > numerically different. Yes. > I think pg_rewind is probably using the insert LSN because it wants to > choose a conservative position as far ahead as possible. It might be > possible to use the flush LSN if the copying logic is carefully > arranged, but I would prefer to keep using the insert LSN if we can. The insert LSN is not crash safe, is this really make sense to use it? For example, the primary has insert LSN 1000, flush LSN 500, the standby sets minRecoveryPoint to 1000, and then the primary crash and restart. The primary now only has LSN 500, but the standby cannot reach consistent until LSN 1000. This doesn’t make sense to me. > I also think it is unfortunate to require client-side tools to be > aware of the exact "end of last record + 1" representation. If both > representations are logically equivalent at those positions, I would > rather have the recovery side accept and normalize them than require > every tool to produce exactly the expected form. Sounds reasonable. > So I wonder whether we should instead normalize minRecoveryPoint when > reading it from the control file, so that these equivalent > representations are treated as the same position. That would also make > the recovery side a bit more robust. > > For example, something like this: > > diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c > index 73b78a83fa7..660c681795d 100644 > --- a/src/backend/access/transam/xlogrecovery.c > +++ b/src/backend/access/transam/xlogrecovery.c > @@ -955,6 +955,20 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr, > { > minRecoveryPoint = ControlFile->minRecoveryPoint; > minRecoveryPointTLI = ControlFile->minRecoveryPointTLI; > + > + /* > + * minRecoveryPoint in the control file is expected to point to the > + * location immediately following the end of the target > + * record. However, some tools may record a location immediately after > + * a WAL segment or page header when the target position is at the > + * beginning of a segment or page. Normalize such values here before > + * further processing. > + */ > + if (XLogSegmentOffset(minRecoveryPoint, wal_segment_size) > + == SizeOfXLogLongPHD) > + minRecoveryPoint -= SizeOfXLogLongPHD; > + else if (minRecoveryPoint % XLOG_BLCKSZ == SizeOfXLogShortPHD) > + minRecoveryPoint -= SizeOfXLogShortPHD; > } > else > { I had thought about this before. To update minRecoveryPoint in place, I think we should make sure that it won't cause any side effects. That means we need to check every places we use minRecoveryPoint. That's why the v1 patch introduces GetEffectiveMinRecoveryPoint() rather than updates it in place. -- Regards, ChangAo Chen -
Re: [BUG] Take a long time to reach consistent after pg_rewind
Kyotaro Horiguchi <horikyota.ntt@gmail.com> — 2026-06-12T08:33:30Z
Hello. At Thu, 11 Jun 2026 15:50:19 +0800, "cca5507" <cca5507@qq.com> wrote in > > I think pg_rewind is probably using the insert LSN because it wants to > > choose a conservative position as far ahead as possible. It might be > > possible to use the flush LSN if the copying logic is carefully > > arranged, but I would prefer to keep using the insert LSN if we can. > > The insert LSN is not crash safe, is this really make sense to use it? For > example, the primary has insert LSN 1000, flush LSN 500, the standby > sets minRecoveryPoint to 1000, and then the primary crash and restart. > The primary now only has LSN 500, but the standby cannot reach > consistent until LSN 1000. This doesn’t make sense to me. My understanding had been that the state produced by pg_rewind only needed to be valid with respect to the source server at the time pg_rewind was run. If the expectation is that the rewound standby must also remain usable after the source server subsequently goes through crash recovery, then I agree that using the insert LSN becomes harder to justify. > I had thought about this before. To update minRecoveryPoint in place, I think we > should make sure that it won't cause any side effects. That means we need to > check every places we use minRecoveryPoint. That's why the v1 patch introduces > GetEffectiveMinRecoveryPoint() rather than updates it in place. The reason I suggested that approach in my earlier email was simply that, as far as I could tell, that was the only place that needed to interpret a minRecoveryPoint value. That said, since minRecoveryPoint is expected to point to the end of the last required record, I think normalizing the value earlier would also be reasonable. Anyway, if we decide to use the flush LSN instead, then none of this should be necessary. Regards, -- Kyotaro Horiguchi NTT Open Source Software Center
-
Re: [BUG] Take a long time to reach consistent after pg_rewind
surya poondla <suryapoondla4@gmail.com> — 2026-06-15T23:22:06Z
Hi, Thanks ChangAo for v2, and Kyotaro for working through the alternatives. I think v2 is on the right track. For me the Flush LSN looks correct for the below reasons: 1. The standby can reach it. XLOG_SWITCH (driven by archive_timeout) calls XLogFlush(EndPos) with EndPos = segment boundary. xlogreader treats SWITCH specially and advances NextRecPtr to end-of-segment (XLogDecodeNextRecord()), so lastReplayedEndRecPtr lands on the segment boundary and the consistency check fires. With v2, minRecoveryPoint = segment boundary, which is exactly reachable. This restores the implicit producer-side invariant: pg_basebackup's XLOG_BACKUP_END EndRecPtr, UpdateMinRecoveryPoint() via GetCurrentReplayRecPtr(), and the standby-source path in pg_rewind all yield a value record-driven progress can match. Caveat: XLogBackgroundFlush rounds WriteRqst.Write down to a page boundary, and the asyncXactLSN bump only re-targets when we've already flushed past it. On a busy primary the flush LSN can therefore sit mid-record, but that's fine because live insertions advance the standby past whatever endrec we pin. Not the scenario in this bug, just worth noting that no producer-side or recovery-side fix eliminates every theoretical liveness corner. 2. GetXLogInsertRecPtr() reads CurrBytePos from shared memory not durable. If the source crashes after pg_rewind, it recovers to its flush LSN, leaving the target's minRecoveryPoint pinned to a higher insert LSN which is unreachable forever. flush LSN sits at the segment boundary, which is on disk and recovery cannot regress past it. So this approach is crash-safe 3. perform_rewind() does: copy files -> fetch source control file -> get_current_wal_flush_lsn. Any WAL-logged page on source's disk has page-LSN <= source's flush-LSN at the time it was written (FlushBuffer enforces XLogFlush before eviction; hint-bit pages with checksums use XLogSaveBufferForHint + PageSetLSN). Flush LSN is monotonic, so the value dominates every copied page-LSN. FSM/VM pages are exempt by design (freespace.c uses MarkBufferDirtyHint without WAL) but aren't gated by minRecoveryPoint. Unlogged tables are out of scope. Torn pages are closed by the existing full_page_writes=on precondition. Review comments 1. Commit message understates the fix. It only describes the page-header symptom. The crash-safety property is the stronger argument and the one that resolves the back-and-forth on which LSN to use. Suggest adding: a. "Using the flush LSN is also crash-safe with respect to the source: the insert LSN lives only in shared memory and can be lost on a source crash, leaving the standby's minRecoveryPoint ahead of any LSN the source can subsequently reach." 2. Code comment should explain why flush LSN is sufficient. The current "We must replay to the last WAL flush location" doesn't say why. Suggest: a. "Use the source's flush LSN as the target's minRecoveryPoint: every WAL-logged page we copied has page-LSN <= source's flush LSN at copy time (WAL-before-data), and flush LSN is monotonic. We avoid the insert LSN because it can sit one page-header past a record's end at segment boundaries (where no record will end), and it is not durable, a source crash can leave flush LSN behind an insert LSN we already pinned." 3. Worth a comment in rewind_source.h that the callback must only be invoked against a non-standby source, pg_current_wal_flush_lsn() errors out under recovery. 4. No regression test. We can add a regression test under src/bin/pg_rewind/t/. Otherwise +1 on the v2 direction. Regards, Surya Poondla
-
Re: [BUG] Take a long time to reach consistent after pg_rewind
cca5507 <cca5507@qq.com> — 2026-06-16T04:46:18Z
Hi, Thanks for the comments! > 1. Commit message understates the fix. It only describes the page-header symptom. The crash-safety property is the stronger argument and the one that resolves the > back-and-forth on which LSN to use. Suggest adding: > a. "Using the flush LSN is also crash-safe with respect to the source: the insert LSN lives only in shared memory and can be lost on a source crash, leaving the standby's minRecoveryPoint ahead of any LSN the source can subsequently reach." > 2. Code comment should explain why flush LSN is sufficient. The current "We must replay to the last WAL flush location" doesn't say why. Suggest: > a. "Use the source's flush LSN as the target's minRecoveryPoint: every WAL-logged page we copied has page-LSN <= source's flush LSN at copy time (WAL-before-data), and flush LSN is monotonic. We avoid the insert LSN because it can sit one page-header past a record's end at segment boundaries (where no record will end), and it is not durable, a source crash can leave flush LSN behind an insert LSN we already pinned." > 3. Worth a comment in rewind_source.h that the callback must only be invoked against a non-standby source, pg_current_wal_flush_lsn() errors out under recovery. Fixed. > 4. No regression test. We can add a regression test under src/bin/pg_rewind/t/. Currently I don't have a good idea about the test, I will work on it later. Any help is welcome! -- Regards, ChangAo Chen
-
Re: [BUG] Take a long time to reach consistent after pg_rewind
surya poondla <suryapoondla4@gmail.com> — 2026-06-29T18:53:27Z
Hi ChangAo, Thanks for the v3, the commit message, in-line comment, and the rewind_source.h note all look good On the test front: I don't think a hang-detection test can be made reliable. The bug requires the source's insert LSN to be exactly segment_boundary + SizeOfXLogLongPHD with no further WAL activity, but bgwriter's periodic LogStandbySnapshot emits a RUNNING_XACTS which can advance the insert LSN nondeterministically between pg_switch_wal() and the rewind. In my reproduction bgwriter ended the hang after ~9s; that's the kind of timing we don't want in CI. The deterministic alternative is to parse pg_controldata on the target after pg_rewind and assert minRecoveryPoint does not land at "boundary + SizeOfXLogLongPHD". That's a direct check on the patched behavior independent of source idleness or replay timing. It doesn't exercise the integration property that the rewound node reaches consistency without further upstream WAL. So I am not sure if this testcase is a complete one in our scenario. Regards, Surya Poondla