Réplicas Laravel : l'ancienne valeur revient après l'enregistrement
Si votre application Laravel envoie les écritures à un serveur de base de données et les lectures à des réplicas, vous pouvez tomber sur un bug invisible en local : un utilisateur modifie le titre d'un article, enregistre, revient sur la page et voit l'ancien titre. Rien n'a échoué. La lecture est allée vers un réplica qui n'avait pas encore copié le changement. Laravel 13.38, sorti en octobre 2026, ajoute deux outils pour ce cas précis.
Pourquoi sticky ne vous sauve pas
Une connexion lecture/écriture dans config/database.php a une option sticky. Activée, elle envoie vers le serveur d'écriture les lectures qui suivent une écriture. Le piège, c'est la portée : cela ne dure que jusqu'à la fin de la requête. Un formulaire classique, c'est un PUT qui redirige vers un GET, donc deux requêtes. La redirection repart d'une connexion neuve, et la page affichée après l'enregistrement lit de nouveau sur un réplica.
La correction côté utilisateur : PreventStaleDatabaseReads
Le nouveau middleware garde la trace de l'écriture d'une requête à l'autre en plaçant un horodatage dans la session. Après une requête qui écrit sur la connexion par défaut, les lectures de cet utilisateur vont au serveur d'écriture pendant deux secondes, puis reviennent aux réplicas. Ajoutez-le au groupe web dans bootstrap/app.php :
use Illuminate\Foundation\Http\Middleware\PreventStaleDatabaseReads;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
PreventStaleDatabaseReads::class,
]);
})
Ou essayez-le d'abord sur quelques routes, avec une fenêtre plus longue si vos réplicas en ont besoin :
Route::middleware(PreventStaleDatabaseReads::for(15))->group(function () {
// routes where a user edits and then views
});
Choisissez la fenêtre d'après vos propres métriques de retard, pas au jugé. Une fenêtre plus courte que le retard réel laisse passer l'ancienne donnée. Trois limites à connaître avant de compter dessus : il faut une session, seule la connexion par défaut est suivie, et seul l'utilisateur qui a écrit est couvert. Un autre utilisateur qui charge la même page peut encore recevoir l'ancienne copie.
La correction côté code : DB::usingWritePdo()
Certains traitements ont besoin de données fraîches quel que soit l'auteur de l'écriture, par exemple un job lancé juste après qu'un webhook a modifié une ligne. onWriteConnection() existe déjà, mais ne s'applique qu'à la requête qu'elle démarre. Chargez une relation depuis le résultat et cette requête part sur un réplica. La nouvelle méthode envoie toutes les lectures du callback au serveur d'écriture, puis rétablit le réglage précédent, même si le callback lève une exception :
$drafts = DB::usingWritePdo(function () use ($email) {
$user = User::query()->where('email', $email)->first();
return $user?->posts()->whereNull('published_at')->get();
});
Le code déjà placé dans DB::transaction() n'en a pas besoin : dans une transaction, les requêtes utilisent de toute façon le serveur d'écriture.
Les workers de file ont le problème inverse
Dans une requête web, sticky s'arrête avec la réponse. Un processus queue:work garde la même connexion d'un job à l'autre : un seul job qui écrit peut envoyer les lectures de tous les jobs suivants vers le serveur d'écriture jusqu'au redémarrage du worker. Laravel 13.36 a ajouté une remise à zéro optionnelle, Worker::$flushStickyConnections = true dans le boot() d'un service provider. Elle est désactivée par défaut : vérifiez les jobs qui lisent juste après l'écriture d'un autre job avant de l'activer. Avec le driver de file database, donnez aussi à la table des jobs sa propre connexion via DB_QUEUE_CONNECTION, sinon la réservation d'un job remet le drapeau.
Le tester sans se tromper soi-même
Chaque entrée du journal des requêtes a une clé readWriteType, read ou write : un test peut enregistrer, demander la page, et vérifier que la première requête était un write. Utilisez DatabaseMigrations ou DatabaseTruncation pour ce test. RefreshDatabase enveloppe chaque test dans une transaction, toute requête en transaction utilise le serveur d'écriture, et l'assertion passerait même sans le middleware.
usingWritePdo().Prochaine étape : si vous avez des réplicas, relevez votre pire retard de réplication, ajoutez PreventStaleDatabaseReads aux routes où l'on modifie puis affiche, réglez la fenêtre au-dessus de ce retard, et écrivez le test readWriteType sans RefreshDatabase. Les deux fonctions sont signées Jack Bayliss, dans #62078 et #62099. Source : Laravel News, Laravel Read Replicas: Prevent Stale Reads After a Write.
Ce que disent les lecteurs