Re: Implement waiting for wal lsn replay: reloaded
Alexander Korotkov <aekorotkov@gmail.com>
On Wed, Jul 8, 2026 at 3:08 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > On Mon, Jul 6, 2026 at 10:17 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > > > On Mon, Jul 6, 2026 at 9:49 PM Xuneng Zhou <xunengzhou@gmail.com> wrote: > > > > > > Hi Heikki, > > > > > > Thanks for looking into this! > > > > > > On Mon, Jul 6, 2026 at 7:04 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote: > > > /* > > > > > * Apply the record > > > > > */ > > > > > ApplyWalRecord(xlogreader, record, &replayTLI); > > > > > > > > > > /* > > > > > * Wake up processes waiting for standby replay, write, or flush > > > > > * LSN to reach current replay position. Replay implies that the > > > > > * WAL was already written and flushed to disk, so write and flush > > > > > * waiters can be woken at the replay position too. > > > > > */ > > > > > WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, > > > > > XLogRecoveryCtl->lastReplayedEndRecPtr); > > > > > WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, > > > > > XLogRecoveryCtl->lastReplayedEndRecPtr); > > > > > WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, > > > > > XLogRecoveryCtl->lastReplayedEndRecPtr); > > > > > > > > That's not wrong, but I've got some comments: > > > > > > > > 1. It's reading XLogRecoveryCtl->lastReplayedEndRecPtr without a lock or > > > > atomics. That's ok, no other process modifies lastReplayedEndRecPtr, but > > > > it feels a little dirty. > > > > > > > > 2. We're now doing three extra function calls on every WAL record. This > > > > is a very hot path, and most of the time, we'll just take the fast path > > > > in WaitLSNWakeup to return without doing anything. Andres and others > > > > assumed up-thread that it's negligible (we used to have pre-checks here > > > > in the caller), but I wonder if you did any performance testing? > > > > > > Agreed, this is a hot path. The performance impact of these extra > > > calls doing real work hasn't been measured yet. I'll do some testing. > > > > > > > 3. There are other "wakeup" calls inside ApplyWalRecord(), to wake up > > > > walsenders and walreceivers. They could perhaps use the same wait-lsn > > > > machinery now, but that's v20 material. However, I think these > > > > WaitLSNWakeup() calls should also be moved inside ApplyWalRecord(), so > > > > that we'd have all the wakeup actions in one place. > > > > > > + 1. This makes the code safer and more readable. > > > > > > > 4. Once you move those calls inside ApplyWalRecord(), like this: > > > > > > > > > @@ -1979,20 +1979,30 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl > > > > > /* > > > > > * Update lastReplayedEndRecPtr after this record has been successfully > > > > > * replayed. > > > > > */ > > > > > SpinLockAcquire(&XLogRecoveryCtl->info_lck); > > > > > XLogRecoveryCtl->lastReplayedReadRecPtr = xlogreader->ReadRecPtr; > > > > > XLogRecoveryCtl->lastReplayedEndRecPtr = xlogreader->EndRecPtr; > > > > > XLogRecoveryCtl->lastReplayedTLI = *replayTLI; > > > > > SpinLockRelease(&XLogRecoveryCtl->info_lck); > > > > > > > > > > + /* > > > > > + * Wake up processes waiting for standby replay, write, or flush LSN to > > > > > + * reach current replay position. Replay implies that the WAL was already > > > > > + * written and flushed to disk, so write and flush waiters can be woken at > > > > > + * the replay position too. > > > > > + */ > > > > > + WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY, xlogreader->EndRecPtr); > > > > > + WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, xlogreader->EndRecPtr); > > > > > + WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH, xlogreader->EndRecPtr); > > > > > + > > > > > /* ------ > > > > > * Wakeup walsenders: > > > > > * > > > > > * On the standby, the WAL is flushed first (which will only wake up > > > > > * physical walsenders) and then applied, which will only wake up logical > > > > > * walsenders. > > > > > > > > It becomes clear that you don't actually need the memory barrier inside > > > > WaitLSNWakeup(). Not sure if they're needed for other callers, but here > > > > we have just released a spinlock, which acts as a memory barrier. It > > > > might not be worth relaxing, but it does seem a little silly. > > > > > > If we made the move here, I think the memory barrier could be relaxed > > > since other callers are guarded by either the spinlock or full-barrier > > > atomic write already. We might also want to make the contract of > > > > OK, the 'if' here is redundant... > > After revisiting the memory barrier in WaitLSNWakeup and why it is > introduced there in a80a593ab63 rather than recalling it from memory, > I think relaxing it here could be unsafe. > > In WaitLSNWakeup(), use pg_atomic_read_membarrier_u64() in the > fast-path check so the waker's preceding position store is globally > visible before minWaitedLSN is read. > > Without the barrier in WaitLSNWakeup(), this interleaving is possible: > > Initial: > minWaitedLSN = PG_UINT64_MAX > replayLSN = 90 > > Waiter: > stores minWaitedLSN = 100 > reads replayLSN before the waker publishes the new replay position > sees replayLSN = 90 > decides it should sleep > > Waker: > publishes replayLSN = 100 > reads old minWaitedLSN = PG_UINT64_MAX > skips the wakeup > > Then the waiter goes to sleep even though replay has reached its > target LSN. To avoid this, we still need to make sure that the > publication of replayLSN precedes the read of minWaitedLSN, so that > the waker cannot decide "nobody is waiting" before its own progress is > still not visible to the waiter. Yes, I also think the memory barrier for waker between publishing replayLSN and reading minWaitedLSN is required. However, sequence of three WaitLSNWakeup() calls makes 3 memory barriers while only one is required. We could introduce a hierarchy for WAIT_LSN_TYPE_STANDBY_*: WAIT_LSN_TYPE_STANDBY_FLUSH implies WAIT_LSN_TYPE_STANDBY_WRITE, WAIT_LSN_TYPE_STANDBY_REPLAY implies WAIT_LSN_TYPE_STANDBY_WRITE and WAIT_LSN_TYPE_STANDBY_FLUSH. Then ApplyWalRecord() can call WaitLSNWakeup() only once and make only 1 memory barrier. ------ Regards, Alexander Korotkov Supabase
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Clean up 019_replslot_limit.pl comments
- 0b866bb90368 19 (unreleased) landed
-
Stabilize 019_replslot_limit.pl: wait on slot restart_lsn
- cdb1d1cf1de6 19 (unreleased) landed
-
Fix memory ordering in WAIT FOR LSN wakeup mechanism
- a80a593ab636 19 (unreleased) landed
-
Improve WAIT FOR LSN test coverage
- cb096e6d6981 19 (unreleased) landed
-
Remove redundant WAIT FOR LSN caller-side pre-checks
- df9f938ca2ec 19 (unreleased) landed
-
Use barrier semantics when reading/writing writtenUpto
- dfb690dd5237 19 (unreleased) landed
-
Use replay position as floor for WAIT FOR LSN standby_(write|flush)
- cba67b5b87f9 19 (unreleased) landed
-
Wake standby_write/standby_flush waiters from the WAL replay loop
- e7cd592174d9 19 (unreleased) landed
-
Minimal fix for WAIT FOR ... MODE 'standby_flush'
- 29e7dbf5e4da 19 (unreleased) landed
-
Avoid syscache lookup while building a WAIT FOR tuple descriptor
- 834038c1f8d5 19 (unreleased) landed
-
Document that WAIT FOR may be interrupted by recovery conflicts
- 10484c2cc75b 19 (unreleased) landed
-
Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()
- 7e8aeb9e483d 19 (unreleased) landed
- f30848cb05d4 19 (unreleased) landed
-
Wake LSN waiters before recovery target stop
- 20a8f783e15c 19 (unreleased) landed
-
Remove redundant pg_unreachable() after elog(ERROR) from ExecWaitStmt()
- 30776ca46865 19 (unreleased) landed
-
Revert "Use WAIT FOR LSN in PostgreSQL::Test::Cluster::wait_for_catchup()"
- e54ce0b2da62 19 (unreleased) landed
-
Fix variable usage in wakeupWaiters()
- bf308639bfcf 19 (unreleased) landed
-
Add tab completion for the WAIT FOR LSN MODE option
- 76948337f724 19 (unreleased) landed
-
Add the MODE option to the WAIT FOR LSN command
- 49a181b5d634 19 (unreleased) landed
-
Extend xlogwait infrastructure with write and flush wait types
- 7a39f43d885b 19 (unreleased) landed
-
Unify error messages
- 502e256f2262 19 (unreleased) cited
-
Optimize shared memory usage for WaitLSNProcInfo
- 75e82b2f5a6f 19 (unreleased) landed
-
Fix WaitLSNWakeup() fast-path check for InvalidXLogRecPtr
- ede6acef4967 19 (unreleased) landed
-
Fix incorrect function name in comments
- 23792d738158 19 (unreleased) landed
-
Add infrastructure for efficient LSN waiting
- 3b4e53a075ea 19 (unreleased) landed
-
Add pairingheap_initialize() for shared memory usage
- 8af3ae0d4b36 19 (unreleased) landed
-
Implement WAIT FOR command
- 447aae13b030 19 (unreleased) landed