Volver al blog
LaravelPHPHomelab

Evita lecturas obsoletas con réplicas MySQL en Laravel 13.38

Laravel 13.38 añade una forma práctica de evitar que un usuario vea datos antiguos tras escribir en una base replicada.

Por Isma4 min de lectura

El problema aparece entre una escritura y la siguiente petición

Separar lecturas y escrituras entre un primario y varias réplicas es una configuración útil cuando la aplicación empieza a necesitar repartir carga. El inconveniente es que la réplica puede tardar un tiempo en recibir los cambios realizados en el primario. Un usuario guarda un formulario, recarga la página y, si la lectura cae en una réplica atrasada, puede ver el estado anterior.

Laravel ya permite configurar conexiones de lectura y escritura en config/database.php. También ofrece la opción sticky, que mantiene las lecturas en la conexión de escritura después de modificar datos durante la misma petición. Eso resuelve el caso inmediato, pero no cubre la siguiente petición HTTP del usuario.

Una ventana corta para leer del primario

Laravel 13.38 incorpora el middleware PreventStaleDatabaseReads para cubrir ese salto entre peticiones. Tras detectar una escritura en la conexión predeterminada, el middleware guarda una marca temporal en la sesión y dirige temporalmente las lecturas posteriores de ese usuario al primario. Cuando la ventana termina, Laravel vuelve a usar las réplicas configuradas para lectura.

En un homelab con MariaDB o MySQL replicado, este comportamiento encaja bien en el grupo web, porque normalmente ya trabaja con sesión. No cambia la topología ni corrige la replicación: simplemente evita que la experiencia de quien acaba de guardar un cambio dependa de que la réplica se haya puesto al día de inmediato. La decisión importante es ajustar la ventana a la latencia real de tu réplica, no elegir un valor arbitrario y olvidarte de él.

use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventStaleDatabaseReads;
 
->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        PreventStaleDatabaseReads::class,
    ]);
})

Si no quieres aplicarlo a todo el tráfico web, puedes asociarlo solo a las rutas donde la consistencia inmediata importa más. El middleware también permite crear una instancia con PreventStaleDatabaseReads::for(...) para definir una ventana distinta. Yo empezaría por los flujos de edición, altas y cambios de estado antes de extenderlo a toda la aplicación.

Lecturas puntuales que no pueden ir a una réplica

Hay consultas que no dependen de una ventana temporal, sino que deben salir del primario sin discusión. Por ejemplo, una operación que carga un modelo y después navega por una relación recién modificada. Para ese caso, Laravel 13.38 añade usingWritePdo() en la conexión de base de datos.

El método recibe un callback y fuerza las lecturas realizadas dentro de ese bloque a la conexión de escritura. Al terminar, Laravel restaura el estado anterior de la conexión, incluso si el callback lanza una excepción. Es una opción más acotada que dejar activada la conexión de escritura para el resto del proceso.

$posts = DB::connection()->usingWritePdo(fn () => User::query()
    ->where('email', $email)
    ->first()
    ?->posts()
    ->whereNull('published_at')
    ->get()
);

No usaría este patrón como sustituto general de una configuración correcta de lectura y escritura. Si todo termina yendo al primario, las réplicas dejan de aportar valor y el problema pasa a ser de diseño o de capacidad. Su sitio son los fragmentos concretos donde leer datos atrasados produciría una decisión incorrecta o una interfaz confusa.

La letra pequeña

PreventStaleDatabaseReads no garantiza consistencia para todos los usuarios. Solo protege a la persona cuya sesión registró la escritura, así que otro usuario puede seguir leyendo desde una réplica que todavía no ha recibido el cambio. Tampoco sigue escrituras en conexiones distintas de la conexión predeterminada.

El middleware depende de la sesión, por lo que no es una solución directa para APIs sin estado. Si tu aplicación recibe varias peticiones simultáneas con la misma sesión, conviene probar ese escenario: una petición larga puede coincidir con otra y el comportamiento temporal no sustituye una garantía fuerte de consistencia. Tampoco arregla réplicas detenidas, retrasos persistentes ni errores en la configuración de MariaDB o MySQL.

Antes de activarlo, revisaría que la separación read y write esté realmente configurada y que puedas observar el retraso de replicación. Después elegiría rutas y ventanas pequeñas basándome en el comportamiento de la aplicación. Es una herramienta para reducir lecturas obsoletas percibidas por el usuario, no una excusa para ignorar la salud de las réplicas.


Fuente: Laravel News — Prevent Stale Database Reads in Laravel 13.38 Documentación oficial: Laravel Database — Read and Write Connections

Newsletter

Lo que aprendo programando con IA, cada dos semanas en tu correo.

Cifras reales, errores incluidos. Sin spam y te das de baja con un clic.

Sin spam y te das de baja con un clic. Uso tu correo solo para enviarte esto y la newsletter. Más en la política de privacidad.