Réplicas de lectura en Laravel: datos viejos después de guardar
Si tu aplicación Laravel manda las escrituras a un servidor de base de datos y las lecturas a réplicas, puedes toparte con un fallo que nunca aparece en local: alguien cambia el título de un artículo, guarda, vuelve a la página y ve el título anterior. No falló nada. La lectura fue a una réplica que todavía no había copiado el cambio. Laravel 13.38, publicado en octubre de 2026, trae dos herramientas justo para este caso.
Por qué sticky no te salva
Una conexión de lectura/escritura en config/database.php tiene la opción sticky. Activada, después de una escritura las lecturas siguientes van al servidor de escritura. El problema es el alcance: solo dura hasta el final de esa petición. Un formulario normal es un PUT que redirige a un GET, es decir, dos peticiones. La redirección empieza con una conexión nueva, así que la página que muestras tras guardar vuelve a leer de una réplica.
La solución para usuarios: PreventStaleDatabaseReads
La nueva middleware recuerda la escritura entre peticiones guardando una marca de tiempo en la sesión. Cuando una petición escribe en la conexión por defecto, las lecturas de ese usuario van al servidor de escritura durante dos segundos y luego vuelven a las réplicas. Añádela al grupo web en bootstrap/app.php:
use Illuminate\Foundation\Http\Middleware\PreventStaleDatabaseReads;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
PreventStaleDatabaseReads::class,
]);
})
O pruébala primero en algunas rutas, con una ventana más larga si tus réplicas lo necesitan:
Route::middleware(PreventStaleDatabaseReads::for(15))->group(function () {
// routes where a user edits and then views
});
Elige la ventana según tus propias métricas de retraso de réplica, no a ojo. Una ventana más corta que el retraso real deja pasar el dato viejo. Hay tres límites que conviene saber antes de confiar en ella: necesita sesión, solo vigila la conexión por defecto y solo cubre al usuario que escribió. Otro usuario que cargue la misma página puede seguir recibiendo la copia anterior.
La solución en el código: DB::usingWritePdo()
Hay código que necesita datos frescos sin importar quién escribió, como un job que corre justo después de que un webhook actualice una fila. onWriteConnection() ya existía, pero solo afecta a la consulta que inicia. Si cargas una relación desde el resultado, esa consulta va a una réplica. El nuevo método manda todas las lecturas del callback al servidor de escritura y después restaura la configuración anterior, aunque el callback lance una excepción:
$drafts = DB::usingWritePdo(function () use ($email) {
$user = User::query()->where('email', $email)->first();
return $user?->posts()->whereNull('published_at')->get();
});
El código que ya está dentro de DB::transaction() no lo necesita: las consultas dentro de una transacción usan el servidor de escritura de todos modos.
Los workers de colas tienen el problema contrario
En una petición web, sticky termina con la respuesta. Un proceso queue:work mantiene la misma conexión entre jobs, así que un solo job que escribe puede mandar las lecturas de todos los siguientes al servidor de escritura hasta que el worker se reinicie. Laravel 13.36 añadió un reinicio opcional: Worker::$flushStickyConnections = true en el boot() de un service provider. Viene desactivado, así que revisa los jobs que leen justo después de que otro escriba antes de activarlo. Con el driver de colas database, dale además a la tabla de jobs su propia conexión mediante DB_QUEUE_CONNECTION, o reservar un job vuelve a activar la marca.
Probarlo sin engañarte
Cada entrada del registro de consultas tiene la clave readWriteType, con read o write, así que un test puede guardar, pedir la página y comprobar que la primera consulta fue un write. Usa DatabaseMigrations o DatabaseTruncation en ese test. RefreshDatabase envuelve cada test en una transacción, toda consulta en una transacción usa el servidor de escritura, y la comprobación pasaría incluso sin la middleware.
usingWritePdo().Siguiente paso: si usas réplicas, busca tu peor retraso de replicación, añade PreventStaleDatabaseReads a las rutas donde se edita y luego se ve, pon la ventana por encima de ese retraso y escribe el test de readWriteType sin RefreshDatabase. Las dos funciones son de Jack Bayliss, en #62078 y #62099. Fuente: Laravel News, Laravel Read Replicas: Prevent Stale Reads After a Write.
Notas de los lectores