`PreventStaleDatabaseReads::for(10)`. Esa línea de la versión 13.38.0 de Laravel, publicada el 20 de octubre de 2026, lleva dos semanas rondándome la cabeza. ¿Por qué diez? ¿Por qué el valor por defecto es dos? La respuesta es que se trata de una suposición, razonable, sobre cuánto suelen ir retrasadas tus réplicas. Este mes voy a añadir este middleware al grupo web en un par de proyectos. Pero la versión también trae `DB::usingWritePdo()`, una novedad mucho más discreta, y es la que preferiría que los equipos aprendieran primero.
<?php
use Illuminate\Support\Facades\DB;
$posts = DB::usingWritePdo(
fn () => User::query()
->where('email', $email)
->first()
?->posts()
->whereNull('published_at')
->get()
);Esto es lo que hace realmente el middleware. Cuando una petición escribe en la conexión por defecto, Laravel guarda una marca de tiempo en la sesión y, durante los dos segundos siguientes, las lecturas de ese usuario van a la conexión de escritura en lugar de a una réplica. Así, el usuario que se ha cambiado el nombre ve el nombre nuevo tras la redirección. Nadie más queda cubierto. Solo funciona con sesión, así que tu API sin estado se queda sin nada. Y las escrituras en conexiones que no son la de por defecto ni siquiera se registran. En la práctica es leer tus propias escrituras en una sola pestaña del navegador, durante un rato corto, lo cual es bastante más limitado de lo que parece.
Hay buenos argumentos para tratarlo como la solución completa. La mayoría de los reportes de lecturas obsoletas son justo el caso posterior a la redirección: alguien guarda un formulario, la página se recarga, sigue apareciendo el valor antiguo y a soporte le llega un ticket diciendo que el botón de guardar no funciona. El middleware elimina toda esa categoría de tickets con una línea en `bootstrap/app.php` y sin tocar el código de dominio. Marcar a mano la frescura en cada punto de llamada llena tus servicios de fontanería de conexiones, y eso es fácil de olvidar. Siendo justo, un comportamiento por defecto del framework que cubre el caso común es muy del estilo de Laravel, y casi siempre es la decisión correcta.
Aquí es donde yo me bajo del tren. Una ventana de tiempo esconde el problema sin hacerlo desaparecer. A las 03:12 de una noche en la que tu réplica se queda nueve segundos atrás por una migración larga, la ventana de dos segundos sigue siendo de dos segundos, y el bug vuelve justo cuando menos capacidad tienes para razonar sobre él. Peor aún, los bugs que cuestan dinero de verdad suelen estar completamente fuera de la sesión. Piensa en un job en cola que lee el stock justo después de un ajuste, o en un handler de webhook que comprueba permisos justo después de un cambio de rol. Sin sesión, ahí no hay protección.
Por eso me importa más `usingWritePdo()`. El antiguo `onWriteConnection()` tenía un agujero que el artículo original explica bien: la consulta principal va al primario, pero la relación que cargas a partir de ella después puede acabar, sin avisar, en una réplica. La nueva forma con callback fija todas las lecturas de dentro a la conexión de escritura y restaura la configuración anterior al terminar, incluso si se lanza una excepción. Así que el fragmento de abajo es una declaración de intenciones. Quien lo lea más adelante sabe que esa consulta tiene que ver la última escritura, y por qué cuesta un viaje de ida y vuelta al primario.
Sí, es más código. Mejor. La frescura es un requisito de negocio que casualmente parece infraestructura, y los requisitos tienen que estar donde la gente pueda leerlos. Si alguien que revisa puede ver que la comprobación del estado del pedido va al primario y la barra lateral del panel va a una réplica, eso es un diseño que se puede discutir en un pull request. Una ventana de middleware es un ajuste que una persona configuró una vez y que nadie recuerda haber cambiado. (Un amigo que programa en Go me cuenta que ellos lo resuelven con un flag en el contexto que pasan por once firmas de funciones. Nosotros tenemos un closure. Me quedo con el closure.)
El resto de la 13.38 es buen mantenimiento. `lazy()` y `lazyById()` ahora liberan el bloque anterior mientras iteran. El lock de un job único ya no se queda atascado cuando falla el despacho de un job programado. Se han corregido los alias de `ofMany()` dentro de closures. Y `Rule::unique()->where()` ahora acepta floats. Nada de esto cambia cómo diseñas, pero el arreglo del lock en concreto explica unas cuantas mañanas de «¿por qué no se ha ejecutado el informe diario?» que he vivido.
Así que activa el middleware y úsalo como red de seguridad mientras localizas las lecturas que de verdad necesitan estar frescas y las envuelves a propósito. Sospecho que muchos equipos no estarán de acuerdo conmigo y dirán que anotar lecturas es prematuro hasta que el retraso de las réplicas aparezca en tus logs. Así que cuéntame: en tu código, ¿dónde te hace daño de verdad el retraso de las réplicas, y alguna vez ocurre dentro de una petición con sesión?




Comentarios
Aún no hay comentarios — escribe el primero.
Inicia la conversación
Sin cuenta ni contraseña — introduce tu correo y te enviamos un enlace de acceso de un solo uso. ¿Primera vez? Todo se configura automáticamente.
Tu valoración se aplicará automáticamente al iniciar sesión.
Revisa tu bandeja de entrada
Hemos enviado un enlace de acceso a …. Ábrelo en este dispositivo — esta pestaña te conectará automáticamente.
¿No llega nada? Mira en la carpeta de spam — y marca el correo como «No es spam» para que la próxima vez llegue directo.