veilletech.fr
13 nov. Feed du jour
#06 LARAVEL Article

Laravel 13.40 : savoir si le réplica a reçu l'écriture

Plus de délai deviné : on demande au réplica où il en est.

Laravel 13.40 ajoute getLastCommitId(), hasCommitId() et mergeCommitIds() : l'application relève la position de réplication du primaire après une écriture, puis demande au réplica s'il l'a atteinte. La fenêtre fixe de deux secondes de PreventStaleDatabaseReads cède la place à une vérification exacte, sur MySQL, MariaDB et PostgreSQL.

4 min de lectureavancévidéo 1:23
Partager
Sommaire7 sections
  1. Ce qui se passe
  2. Les trois méthodes
  3. Les prérequis
  4. Lire ses propres écritures
  5. Dans un job
  6. Où s'exécute la vérification
  7. À retenir

Ce qui se passe

Laravel 13.38 avait apporté le middleware PreventStaleDatabaseReads : après une écriture, les lectures de l'utilisateur vont au primaire pendant deux secondes, une estimation fixe du retard de réplication. Trop courte, il relit du périmé ; trop longue, le primaire travaille pour rien. Laravel 13.40 ajoute trois méthodes de connexion, contribuées par Tobias Petry (PR #62198), qui remplacent l'estimation par une question posée au réplica.

Les trois méthodes

Les prérequis

Base Réglages serveur wait
MySQL log_bin, gtid_mode = ON, enforce_gtid_consistency = ON oui
MariaDB log_bin oui
PostgreSQL aucun à partir de PostgreSQL 19
SQLite, SQL Server non pris en charge non

MySQL 8 active le journal binaire par défaut, pas les GTID ; MariaDB n'active pas le journal binaire. Un réglage manquant lève une RuntimeException qui le nomme. Un service managé peut refuser ces modifications.

Lire ses propres écritures

Le schéma du billet : un middleware du groupe web, placé après la session, garde l'identifiant après chaque requête qui écrit, et le vérifie à la suivante.

RéplicaPrimaireApplicationNavigateurRéplicaPrimaireApplicationNavigateuralt[réplica à jour][réplica en retard]PUT, enregistrement1Écriture2getLastCommitId()3Redirection, id en session4GET page suivante5hasCommitId(id)6Lectures7Lectures8
Après une écriture, la requête suivante ne va au primaire que si le réplica est en retard

Une nouvelle écriture avant que le réplica ait rattrapé ? mergeCommitIds() fusionne les deux identifiants dans la session. Le coût : une requête de plus sur le réplica tant qu'un identifiant est en attente, une sur le primaire par requête qui écrit. Si la réplication s'arrête, l'utilisateur reste sur le primaire et voit des données à jour. Le middleware vérifie sans wait, pour ne pas retenir la réponse.

Dans un job

Un job envoyé juste après une écriture peut démarrer avant que le réplica ait la ligne. Une esquisse :

PHP
$order = DB::transaction(fn () => Order::create($data));

// Après le commit : relevé dans la transaction, l'identifiant n'inclurait pas la commande.
SendOrderConfirmation::dispatch($order->id, DB::getLastCommitId());
app/Jobs/SendOrderConfirmation.phpPHP
public function handle(): void
{
    $replicaReady = DB::hasCommitId($this->commitId, wait: 2);

    $replicaReady
        ? $this->send()
        : DB::usingWritePdo(fn () => $this->send());
}

Deux pièges. Passez l'identifiant de la commande, pas le modèle : avec SerializesModels, Laravel recharge le modèle avant handle(), sur un réplica, et un réplica en retard lève ModelNotFoundException. Et préférez usingWritePdo() à useWriteConnectionWhenReading() : un worker réutilise sa connexion d'un job à l'autre, et le premier rétablit le réglage à la fin du callback.

Où s'exécute la vérification

hasCommitId() est une lecture : elle va là où vont les lectures de la requête. Dans une transaction, avec sticky après une écriture, ou quand les lectures sont forcées vers l'écriture, c'est le primaire — qui répond true, ce qui reste juste puisque les lectures suivantes y vont aussi. Une connexion garde un seul PDO de lecture par requête ; la suivante peut tomber sur un autre réplica, donc on vérifie à chaque requête, sans mettre le résultat en cache.

Source : Laravel News, en anglais. Voir aussi la fiche du 22 octobre sur PreventStaleDatabaseReads.