6 min read
PostgreSQL 19 WAIT makes read-your-writes a contract
A replica read needs a decision
A user updates a record and immediately opens it again. If the application sends that second read to an asynchronous standby, it may see the old value: the primary has committed the write, but the standby has not replayed it yet. PostgreSQL’s Beta 4 announcement describes the new WAIT FOR command as a way to wait for a standby to reach a chosen log sequence number before running a SELECT (PostgreSQL 19 Beta 4 announcement).
That gives me a useful alternative to routing every read after a write back to the primary. But I would not treat it as a free consistency switch. The application is choosing to spend time waiting on the replica, and needs a defined answer for what happens when that wait takes too long or cannot succeed.
I would use the wait when the user’s next read needs to reflect a particular committed write, and when keeping that read on the replica is worth a bounded delay. If the screen needs an immediate response more than it needs to use a replica, routing to the primary is simpler. If the read is unrelated to the write, waiting buys nothing. The decision belongs to the request path that knows whether this read depends on that write.
Carry the write’s position to the read
The wait needs a target LSN: a position in the WAL stream that represents progress the standby must reach. The safe shape is to commit the write on the primary, obtain a position at or after that commit, then wait on the standby before issuing the dependent read. PostgreSQL documents WAIT modes for waiting until WAL is written, flushed, or replayed, and the default mode is standby_replay (PostgreSQL 19 WAIT command).
For example, the application can keep the write and follow-up read on separate steps:
-- On the primary, after the write transaction has committed.
SELECT pg_current_wal_lsn();
-- On the standby, using the LSN returned above.
WAIT FOR LSN '0/306EE20'
WITH (MODE 'standby_replay', TIMEOUT '250ms', NO_THROW);
-- Only after a successful wait, issue the dependent SELECT.
SELECT id, status
FROM orders
WHERE id = 42;
The LSN shown is an example, not a value to reuse. In a real application, pass the LSN returned by the primary to the replica connection. PostgreSQL documents pg_current_wal_lsn() as returning the current write-ahead log insert location (PostgreSQL WAL administration functions). Calling it after commit can give the application a conservative target: the position may include WAL beyond its own transaction, so the replica might wait for a little more progress than strictly necessary. I would rather make that trade-off explicit than pretend the target is an exact marker for only one row change.
The read should use a fresh statement context after the wait. A wait cannot make an already established snapshot show a later commit. In particular, I would not place the wait inside a long-running transaction that has already taken the snapshot the read will use. I would also verify the behavior with the application’s actual transaction isolation and connection-pooling setup, rather than assuming that the SQL sequence alone guarantees a fresh view.
A timeout is part of the API
The example sets a timeout because the unbounded default is a poor fit for a user-facing request. The PostgreSQL WAIT documentation says that a missing or zero timeout waits indefinitely; with NO_THROW, the command reports the outcome as a status rather than raising an error (WAIT timeout and status behavior). That turns an implementation detail into a product decision: how long is this request allowed to wait, and what should the caller do if the replica has not caught up by then?
I would make the timeout shorter than the remaining request budget, leaving room for the read itself and for the application to handle the result. The value in the example is illustrative; I would choose a real limit from the service’s latency budget and test it against observed lag. A timeout is not a consistency success. If the application proceeds to read the standby after a timeout, it may return stale data. That can be acceptable for some screens, but it must be an intentional fallback rather than an accidental consequence of swallowing an error.
There are a few honest choices after a timeout. The application can retry the wait if its remaining deadline allows; route the dependent read to the primary; return a response that asks the user to retry; or accept a potentially stale read where that is safe. I would generally route to the primary for a user action that explicitly expects its own update to appear, provided the primary is available and the request still has time. For a less critical view, I might return the latest replica value and communicate that it can lag. I would avoid automatic retries without a total deadline: repeated waits can turn replica lag into a request that occupies a connection for far longer than the caller expects.
Test the lag, not just the happy path
The new command can reduce application-side polling and make the consistency boundary clearer. It does not remove the need to decide what the application promises when replicas lag. PostgreSQL’s Beta 4 release notes also caution that beta features may change before general availability (PostgreSQL 19 Beta 4 announcement), so I would test against the exact PostgreSQL build planned for deployment and check the final documentation before relying on the syntax or behavior in production.
I would test the whole request path with controlled replica lag: write on the primary, capture the target LSN, wait on the intended standby, and perform the dependent read only when the wait succeeds. Then I would exercise timeout and connection failure paths, including the fallback route, and verify that the application does not silently claim read-your-writes after a failed wait. I would also check what happens when a connection is sent to a server that is no longer operating as a standby; the documented standby wait modes can report an error if the server is promoted before reaching the target LSN (WAIT command behavior).
The choice is not “replicas or consistency.” It is whether this particular read should wait for a replica, how much of the request budget that wait may consume, and what the application promises when the budget runs out. WAIT makes that contract visible in SQL. It does not make the contract optional.
Drafted with AI assistance, reviewed and edited by Ali.