Postgres recovery_prefetch Stalled Writes for Over 30 Minutes in a Synchronous-Replication Test
Software / news
Postgres recovery_prefetch Stalled Writes for Over 30 Minutes in a Synchronous-Replication Test
Jeremy Schneider's Oct. 20 reproduction kills a standby's walreceiver on a 500-IOPS replica; with prefetch off, the same update applied in under 10 minutes.
Jeremy Schneider of Ardent Performance Computing reported on Oct. 20 that Postgres's recovery_prefetch setting can leave a primary with synchronous replication unable to accept writes for more than 30 minutes after its standby's WAL receiver is killed. In his reproduction, TPS "drops to zero and all writes hang."
Schneider says he is "leaning toward disabling the recovery_prefetch setting across the board." The Postgres 18 documentation lists the default as try, so unless an operator has changed it, the prefetching is already on wherever the operating system supports read-ahead advice.
How a read-ahead setting stalls writes
With synchronous replication, the primary cannot commit without a connected standby. Schneider writes that if the connection disappears, the primary "can only sit and wait, blocking all writes."
The standby does not resume streaming from the primary until it has replayed the WAL it already has on disk. That is where recovery_prefetch enters. The post says it turns large sequential I/O on the primary into 8k random reads on replicas, amplifying IOPS "as much as 16x" for the same bytes. On a replica with an IOPS cap, replay crawls, and the primary waits the whole time.
The documentation describes the feature more gently: it tries to "prefetch blocks that are referenced in the WAL that are not yet in the buffer pool, during recovery," which "can reduce I/O wait times during recovery with some workloads." The failing case is a replica with a tight IOPS budget catching up on a large backlog.
The reproduction, with its numbers
Schneider's test ran a synchronous standby on small servers: 500 IOPS, 2 CPUs and 8GB of RAM. He used pgbench at scale 640, about 10GB, with autovacuum disabled on pgbench_accounts, 5 TPS, and a 5% update, then killed the walreceiver.
| Condition | Replica IOPS | Result |
|---|---|---|
recovery_prefetch on (default try) | At the 500 IOPS cap for about 40 minutes | Writes hung for over 30 minutes |
recovery_prefetch off, full-page writes on | No read IOPS burst | Update applied in under 10 minutes |
On the primary, the disk chart showed roughly 300 IOPS over about five minutes, while the replica sat at its limit. With prefetch off and full-page writes still enabled, the post reports no burst and no long pause. Both runs are on deliberately small hardware, so the 30 minutes is not a prediction for a production fleet. The reproduction is public at ardentperf-labs/walreceiver-outage-repro, which Schneider links.
This was flagged before
The post cites two earlier reports on the Postgres mailing list. In February 2021, before the feature shipped, Stephen Frost questioned repeat prefetches when full-page writes are enabled, writing: "Just doesn't seem as clear-cut to me when thinking about the FPW-enabled case."
In March 2025, Michael Zhilin of Postgres Pro reported a customer on bare metal with multi-hour replay lag that disappeared when recovery_prefetch was turned off. He observed that prefetching "doesn't consider that shared buffer may be loaded from WAL FPI rather than from disk," as Schneider quotes him. I have not opened those two threads, so both quotations are as Schneider reproduces them.
What to change, and what the post does not settle
Schneider's workarounds are two: disable recovery_prefetch, which he tested with full-page writes left on, or run two standbys with quorum commit (ANY 1). The second costs an extra replica but the primary can commit with either standby.
The post does not list affected Postgres versions. It links the REL_18_STABLE source for the standby state machine, and it does not say which release first showed the behaviour. A reader comment dated Oct. 21 describes a standby with recovery_min_apply_delay = '7d' whose walreceiver never restarted after an OS reboot; Schneider says he did not analyse that case.
The practical check for an operator is short: look at SHOW recovery_prefetch on each standby, and compare the replica's provisioned IOPS with the primary's write volume. No Postgres project fix or advisory is cited in the post, and the next thing to watch is whether the setting's documentation or default changes. It joins the Node.js October security release and the Next.js 16.4.1 advisories on this week's operator checklist.
Sources
More in Software
- 01UUIDv7 Guarantees Uniqueness and Little Else, and Python 3.14's Generator Kept Running Ahead of a Reset ClockRobert Alexander's Oct. 19 post sets RFC 9562's own wording against the ordering, timestamp and unguessability assumptions developers make.
- 02Cloudflare's Security-Audit Skill Started as 450 Lines, and Its Own Numbers Show One Run Finds About Half the BugsThe MIT-licensed skill topped GitHub trending on Oct. 23; Cloudflare's June post on the harness behind it reports 7,245 actionable findings from 128 repositories and claims no false-negative rate.
- 03Chrome's HTML-in-Canvas Trial Runs in Versions 148 to 150, and Chrome's Post and the Spec Name the API DifferentlyThe API draws live DOM, form fields included, into 2D, WebGL and WebGPU canvases; Google's post carries no trial dates and a May 19 update stamp.
- 04Respan's Rust LLM Gateway Claims 0.07 ms Overhead, but the Launch Post Calls It Pre-AlphaThe YC W24 company's open-source gateway has 141 stars, a README that lists a Docker image its Hacker News commenters could not find, and benchmark figures it ran itself.