`PreventStaleDatabaseReads::for(10)`. That one line from the Laravel 13.38.0 release, tagged October 20, 2026, has been bugging me for two weeks. Why ten? Why is the default two? The answer is that it's a guess, a sensible one, about how far behind your replicas usually run. I'll be appending this middleware to the web group on a couple of projects this month. But the release also ships `DB::usingWritePdo()`, a much quieter addition, and that's the one I'd rather teams learn first.
<?php
use Illuminate\Support\Facades\DB;
$posts = DB::usingWritePdo(
fn () => User::query()
->where('email', $email)
->first()
?->posts()
->whereNull('published_at')
->get()
);Here's what the middleware actually does. When a request writes to the default connection, Laravel stores a timestamp in the session, and for the next two seconds that user's reads go to the write connection instead of a replica. So the user who renamed themselves sees the new name after the redirect. Nobody else is covered. It only works with a session, so your stateless API gets nothing. And writes on non-default connections aren't tracked at all. That makes it read-your-own-writes for one browser tab, for a short while, which is narrower than it sounds.
The case for treating it as the whole fix is a strong one. Most stale-read bug reports really are the post-redirect case: someone saves a form, the page reloads, the old value is still there, and support gets a ticket saying the save button is broken. The middleware wipes out that whole class of ticket with one line in `bootstrap/app.php` and no changes to domain code. Marking freshness by hand at every call site spreads connection plumbing through your services, and that's easy to forget. If I'm being fair, a framework default that covers the common case is very much the Laravel way, and it's usually the right call.
Here's where I get off that train. A time window hides the problem without making it go away. At 03:12 on a night your replica falls nine seconds behind because of a long migration, the two-second window is still two seconds, and the bug comes back exactly when you're least able to reason about it. Worse, the bugs that cost real money tend to be outside the session completely. Think of a queued job reading a stock level right after an adjustment, or a webhook handler checking permissions right after a role change. No session means no protection there.
That's why I care more about `usingWritePdo()`. The older `onWriteConnection()` had a hole the source article spells out well: the parent query hits the primary, but the relationship you load from it afterwards can quietly go to a replica. The new callback form pins every read inside it to the write connection and restores the previous setting afterwards, even when an exception is thrown. So the snippet below is a statement about intent. Whoever reads it later knows this lookup has to see the latest write, and why it costs a primary round trip.
Yes, it's more code. Good. Freshness is a business requirement that happens to look like infrastructure, and requirements belong where people can read them. If a reviewer can see that the order status check runs on the primary and the dashboard sidebar runs on a replica, that's a design you can argue about in a pull request. A middleware window is a setting that one person tuned once and nobody remembers changing. (A friend writing Go tells me they handle this with a context flag passed through eleven function signatures. We get a closure. I'll take the closure.)
The rest of 13.38 is good housekeeping. `lazy()` and `lazyById()` now free the previous chunk as they iterate. A unique-job lock no longer stays stuck when dispatching a scheduled job fails. `ofMany()` aliases inside closures got fixed. And `Rule::unique()->where()` now accepts floats. None of these change how you design things, but the lock fix in particular explains a few 'why didn't the daily report run' mornings I've had.
So turn the middleware on, and treat it as a safety net while you go find the reads that really need to be fresh and wrap them on purpose. I suspect plenty of teams will disagree with me and argue that annotating reads is premature until replica lag actually shows up in your logs. So tell me: in your codebase, where does replica lag actually hurt, and is it ever inside a request that has a session?




Comments
No comments yet — be the first.
Open the discussion
No account or password needed — just enter your e-mail and we’ll send you a one-time sign-in link. First time here? You’re set up automatically.
Your rating will be applied automatically after you sign in.
Check your inbox
We’ve sent a sign-in link to …. Open it on this device — this tab will sign you in automatically.
Nothing arrived? Check your spam folder — and mark the mail as "Not spam" so it lands in your inbox next time.