Re: Implement waiting for wal lsn replay: reloaded

From: Xuneng Zhou <xunengzhou(at)gmail(dot)com>
To: Alexander Korotkov <aekorotkov(at)gmail(dot)com>
Cc: Noah Misch <noah(at)leadboat(dot)com>, Heikki Linnakangas <hlinnaka(at)iki(dot)fi>, Andres Freund <andres(at)anarazel(dot)de>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, Peter Eisentraut <peter(at)eisentraut(dot)org>, Thomas Munro <thomas(dot)munro(at)gmail(dot)com>, Álvaro Herrera <alvherre(at)kurilemu(dot)de>, Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com>, pgsql-hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Michael Paquier <michael(at)paquier(dot)xyz>, jian he <jian(dot)universality(at)gmail(dot)com>, Tomas Vondra <tomas(at)vondra(dot)me>, Yura Sokolov <y(dot)sokolov(at)postgrespro(dot)ru>
Subject: Re: Implement waiting for wal lsn replay: reloaded
Date: 2026-07-31 01:46:46
Message-ID: CABPTF7Vo_cWfKbpRW4e+ThrayZz9mV_OpM3Y6WL52ibSV1u4JQ@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Fri, Jul 31, 2026 at 8:58 AM Xuneng Zhou <xunengzhou(at)gmail(dot)com> wrote:
>
> On Thu, Jul 30, 2026 at 10:51 AM Xuneng Zhou <xunengzhou(at)gmail(dot)com> wrote:
> >
> > On Tue, Jul 28, 2026 at 9:25 PM Xuneng Zhou <xunengzhou(at)gmail(dot)com> wrote:
> > >
> > > On Mon, Jul 27, 2026 at 7:03 PM Xuneng Zhou <xunengzhou(at)gmail(dot)com> wrote:
> > > >
> > > > Hi Alexander,
> > > >
> > > > On Thu, Jul 9, 2026 at 8:24 PM Xuneng Zhou <xunengzhou(at)gmail(dot)com> wrote:
> > > > >
> > > > > Hi!
> > > > >
> > > > > As Noah pointed out in [1], auxiliary processes are supported in the
> > > > > facility, but proper cleanup is absent when they exit. I have attached
> > > > > a patch trying to address that as suggested before getting sidetracked
> > > > > for too long with the issue raised in [2].
> > > >
> > > > I'd like to propose a series of patches to do some further clean-ups &
> > > > enhancements. The first few are mostly mechanical, straightforward,
> > > > and the last two are more subtle. I appreciate your inputs/thoughts on
> > > > them.
> > > >
> > > > 1) Patch 1 to address the issue raised by Noah.
> > > >
> > > > 2) Patch 2 to fix two small docs mismatches.
> > > >
> > > > 3) Patch 3 to optimize the WaitLSNLock acquisition in deleteLSNWaiter.
> > > >
> > > > 4) Patch 4 to clarify lsn waiter cleanup after wakeup.
> > > >
> > > > 5) Patch 5 to address a rare issue of deregistration of waiters whose
> > > > target lsn has reached but then flashed back.
> > > >
> > > > Currently, the waiters are removed from the heap by the wakers if
> > > > their target lsn has reached. We offer no re-registration service in
> > > > the reception desk for them assuming they won't need to extend their
> > > > stay. This assumption holds if the lsn is monotonically increasing,
> > > > which is true for most cases. However, there seems to be a catch for
> > > > the wal write/flush lsn from the wal receiver side. In the cases of
> > > > restarting wal receiver, the write/flush lsn could be reset to
> > > > positions lagging behind(the attached restarting cases file summarized
> > > > by Sol with inputs from me shows a matrix of this).
> > > >
> > > > One problematic interleaving, for reset cases 2–4 in the attached
> > > > file, is the following:
> > > >
> > > > Let W be the old published write position, X the waiter’s target, S
> > > > the new streaming segment start, and R the replay position, with:
> > > > max(N, R) < X ≤ W
> > > >
> > > > [T0] A backend registers a standby_write waiter for target X and sleeps.
> > > >
> > > > [T1] The walreceiver publishes writtenUpto = W and calls
> > > > WaitLSNWakeup(W). Because X ≤ W, the waker removes the waiter. from
> > > > the heap, sets inHeap = false, and sets its latch.
> > > >
> > > > [T2] Before the waiter runs, streaming is restarted.
> > > > RequestXLogStreaming() resets shared writtenUpto from W to S. The
> > > > effective standby_write position is now: max(writtenUpto, replay) =
> > > > max(S, R) = R. Therefore the effective position has regressed below X.
> > > >
> > > > [T3] The waiter consumes the old latch notification and rechecks its
> > > > condition. It observes R < X. In the pre-fix code, it goes back to
> > > > sleep without re-registering, even though it is no longer in the
> > > > waiters heap.
> > >
> > > I took a second look at the wording. I had asked Sol to polish this
> > > part for clarity, but the revised version is inaccurate. It should be:
> > >
> > > [T2] Before the waiter runs, streaming is restarted.
> > > RequestXLogStreaming() resets writtenUpto from W to S. The effective
> > > standby_write position becomes max(S, R), which is below X.
> > >
> > > [T3] The waiter consumes the earlier latch notification and rechecks
> > > its condition. It observes that max(S, R) < X. In the pre-fix code, it
> > > waits again without re-registering, even though the waker has already
> > > removed it from the heap.
> > >
> > > Sorry for not double-checking it.
> > >
> > > > [T4] The walreceiver later receives and publishes WAL through X and
> > > > calls WaitLSNWakeup() again. The waiter is absent from the heap, so it
> > > > receives no notification and can remain asleep even though its target
> > > > has now been reached.
> > > >
> > > > One possible fix for the issue that I have considered is to make
> > > > write/flush lsn monotonic so it won't flash back once advanced. This
> > > > seems to work well at first glance. However, it has two shortcomings:
> > > > one is that it can blur the definition of write lsn and another is
> > > > that it won't fix the cross-timeline lsn regression. Currently,
> > > > writtenUpto is the current walreceiver stream’s write frontier. Giving
> > > > that value monotonicity would turn it into the greatest numeric lsn
> > > > ever written by any receiver incarnation. Cross-timeline lsn flashback
> > > > is expected given the fork point is smaller than the pre-restart lsns.
> > > > Extending it across timelines would incorrectly associate progress on
> > > > an old timeline with the new timeline. The patch places the fix in the
> > > > waiter side -- let the backend do a recheck and re-registration if the
> > > > published lsn is regressed.
> > > >
> > > > Another aspect is the test, it's relatively simple to construct the
> > > > failure case, but seems hard to do so deterministically. I spent a lot
> > > > of time(maybe too much for an unconfirmed rare issue) trying to tame
> > > > it in a simple way; it seems hard to orchestrate the startup process,
> > > > wal receiver, waiting backend into above-like interleavings end-to-end
> > > > without using complex synchronization like three injection points. The
> > > > tricky part is to ensure max(N, R) < X ≤ W. We cannot stop the startup
> > > > then the wal receiver to make sure that because we need the former to
> > > > manipulate the later one. The current workaround is to add a synthetic
> > > > helper for removing the heap node even if its target lsn has not
> > > > actually reached, then trigger the real wake-up call by advancing the
> > > > published lsn to check whether the waiter is notified and the wait
> > > > completes. The rationale behind this is that the premature removal
> > > > from the heap without re-registration is the underlying issue
> > > > regardless of the specific lsn types and failure scenarios. That said,
> > > > I am unsure whether this is a proper way or better alternatives exist.
> > > >
> > > > 6) Patch 6 to cover a missing wake-up point for primary-flush waiters [WIP]
> >
> > After learning more of the test/modules, I became less convinced to
> > use an injection point for the proof of re-registration in the test
> > which seems somewhat hacky to me. It works in a non-obvious manner --
> > we infer from the position of the code that the registration occurs,
> > but it is prone to future changes unless we add more comments to
> > explain its usage. Just wondering whether it makes sense to add a
> > helper to probe & prove the waiter is registered in the heap.
> >
>
> Updated the patch 5 according to the methodology. Added a new test
> helper, test_wait_lsn_waiter_is_registered(), that checks whether a
> process is registered in the waiters heap for a specified target LSN,
> wait type, and PID.

Generalized the test_wait_lsn_wakeup helper to specify the wait type
to pair with test_wait_lsn_waiter_is_registered.

--
Regards,
Xuneng Zhou
HighGo Software Co., Ltd.

Attachment Content-Type Size
v3-0005-Re-register-LSN-waiters-after-stale-wakeups.patch application/octet-stream 13.9 KB
v3-0001-Add-wait-for-lsn-process-exit-cleanup-callback.patch application/octet-stream 3.3 KB
v3-0004-Clarify-LSN-waiter-cleanup-after-wakeup.patch application/octet-stream 1.2 KB
v3-0002-Fix-WAIT-FOR-LSN-documentation-examples.patch application/octet-stream 2.9 KB
v3-0003-Avoid-unnecessary-WaitLSNLock-acquisition-after-w.patch application/octet-stream 1.2 KB
v3-0001-Add-wait-for-lsn-process-exit-cleanup-callback.patch application/octet-stream 3.3 KB
v3-0004-Clarify-LSN-waiter-cleanup-after-wakeup.patch application/octet-stream 1.2 KB
v3-0002-Fix-WAIT-FOR-LSN-documentation-examples.patch application/octet-stream 2.9 KB
v3-0003-Avoid-unnecessary-WaitLSNLock-acquisition-after-w.patch application/octet-stream 1.2 KB
v3-0005-Re-register-LSN-waiters-after-stale-wakeups.patch application/octet-stream 13.9 KB

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Peter Smith 2026-07-31 01:58:18 Re: Support EXCEPT for ALL SEQUENCES publications
Previous Message Bharath Rupireddy 2026-07-31 01:15:00 Re: [PATCH] Fix NULL dereference in subscription REFRESH on concurrent DROP