`PreventStaleDatabaseReads::for(10)`. Cette ligne tirée de la release Laravel 13.38.0, taguée le 20 octobre 2026, me trotte dans la tête depuis deux semaines. Pourquoi dix ? Pourquoi deux par défaut ? La réponse, c'est qu'il s'agit d'une estimation, raisonnable, du retard habituel de tes réplicas. Je vais ajouter ce middleware au groupe web sur deux ou trois projets ce mois-ci. Mais la version embarque aussi `DB::usingWritePdo()`, un ajout bien plus discret, et c'est celui-là que je préférerais voir les équipes apprendre en premier.

ProfileQuery.php
<?php

use Illuminate\Support\Facades\DB;

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

Voici ce que fait vraiment le middleware. Quand une requête écrit sur la connexion par défaut, Laravel stocke un timestamp en session et, pendant les deux secondes suivantes, les lectures de cet utilisateur partent sur la connexion d'écriture au lieu d'un réplica. L'utilisateur qui vient de changer son nom voit donc le nouveau nom après la redirection. Personne d'autre n'est couvert. Ça ne fonctionne qu'avec une session, donc ton API stateless n'a droit à rien. Et les écritures sur les connexions autres que celle par défaut ne sont pas suivies du tout. Au final, c'est du read-your-own-writes pour un seul onglet, pendant un court instant, ce qui est plus étroit qu'il n'y paraît.

Il y a pourtant de bons arguments pour y voir la solution complète. La plupart des tickets de lecture périmée concernent vraiment le cas post-redirection : quelqu'un enregistre un formulaire, la page se recharge, l'ancienne valeur est toujours là, et le support reçoit un ticket disant que le bouton Enregistrer est cassé. Le middleware fait disparaître toute cette catégorie de tickets avec une ligne dans `bootstrap/app.php` et sans toucher au code métier. Marquer la fraîcheur à la main à chaque appel, c'est éparpiller de la plomberie de connexion dans tes services, et c'est facile à oublier. Pour être honnête, un comportement par défaut du framework qui couvre le cas courant, c'est tout à fait l'esprit Laravel, et c'est généralement le bon choix.

C'est là que je descends du train. Une fenêtre de temps masque le problème sans le faire disparaître. À 03:12, la nuit où ton réplica prend neuf secondes de retard à cause d'une longue migration, la fenêtre de deux secondes reste de deux secondes, et le bug revient pile au moment où tu es le moins en état d'y réfléchir. Pire, les bugs qui coûtent vraiment de l'argent se trouvent souvent complètement hors session. Pense à un job en file d'attente qui lit un niveau de stock juste après un ajustement, ou à un handler de webhook qui vérifie des permissions juste après un changement de rôle. Pas de session, donc aucune protection.

Voilà pourquoi `usingWritePdo()` m'intéresse davantage. L'ancien `onWriteConnection()` avait un trou que l'article source explique bien : la requête parente tape sur le primaire, mais la relation que tu charges ensuite peut discrètement partir vers un réplica. La nouvelle forme avec callback épingle toutes les lectures qu'elle contient sur la connexion d'écriture, puis restaure le réglage précédent, même si une exception est levée. Le snippet ci-dessous est donc une déclaration d'intention. Quiconque le relira plus tard saura que cette recherche doit voir la dernière écriture, et pourquoi elle coûte un aller-retour vers le primaire.

Oui, c'est plus de code. Tant mieux. La fraîcheur est une exigence métier déguisée en infrastructure, et les exigences doivent se trouver là où on peut les lire. Si un reviewer voit que la vérification du statut de commande passe par le primaire et que la sidebar du dashboard passe par un réplica, c'est un design dont on peut débattre dans une pull request. Une fenêtre de middleware, c'est un réglage qu'une personne a ajusté un jour et que plus personne ne se souvient d'avoir modifié. (Un ami qui code en Go m'explique qu'ils gèrent ça avec un flag de contexte transmis à travers onze signatures de fonctions. Nous, on a une closure. Je prends la closure.)

Le reste de la 13.38, c'est du bon ménage. `lazy()` et `lazyById()` libèrent désormais le chunk précédent au fil de l'itération. Un verrou de job unique ne reste plus bloqué quand le dispatch d'un job planifié échoue. Les alias `ofMany()` dans les closures ont été corrigés. Et `Rule::unique()->where()` accepte maintenant les floats. Rien de tout ça ne change ta façon de concevoir, mais le correctif du verrou, en particulier, explique quelques matinées « pourquoi le rapport quotidien n'a pas tourné ? » que j'ai vécues.

Alors active le middleware, et considère-le comme un filet de sécurité pendant que tu pars à la recherche des lectures qui ont vraiment besoin d'être fraîches pour les encapsuler volontairement. Je me doute que pas mal d'équipes ne seront pas d'accord et diront qu'annoter les lectures est prématuré tant que le lag des réplicas n'apparaît pas dans tes logs. Alors dis-moi : dans ta codebase, où est-ce que le lag des réplicas fait vraiment mal, et est-ce que ça arrive parfois dans une requête qui a une session ?