`PreventStaleDatabaseReads::for(10)`. Diese eine Zeile aus dem Release Laravel 13.38.0, getaggt am 20. Oktober 2026, lässt mich seit zwei Wochen nicht los. Warum zehn? Warum ist der Default zwei? Die Antwort: Es ist geraten, vernünftig geraten, wie weit deine Replicas normalerweise hinterherhinken. Ich werde die Middleware diesen Monat in ein paar Projekten an die web-Gruppe hängen. Aber das Release enthält auch `DB::usingWritePdo()`, eine viel unauffälligere Neuerung, und genau die sollten Teams meiner Meinung nach zuerst lernen.
<?php
use Illuminate\Support\Facades\DB;
$posts = DB::usingWritePdo(
fn () => User::query()
->where('email', $email)
->first()
?->posts()
->whereNull('published_at')
->get()
);So funktioniert die Middleware tatsächlich: Wenn ein Request auf die Default-Connection schreibt, legt Laravel einen Zeitstempel in der Session ab, und für die nächsten zwei Sekunden gehen die Reads dieses Users an die Write-Connection statt an ein Replica. Wer seinen Namen ändert, sieht also nach dem Redirect den neuen Namen. Alle anderen gehen leer aus. Ohne Session läuft gar nichts, deine zustandslose API bekommt also nichts davon ab. Und Schreibvorgänge auf anderen Connections als der Default-Connection werden überhaupt nicht erfasst. Das ist Read-your-own-writes für einen einzigen Browser-Tab, für kurze Zeit, und damit enger gefasst, als es klingt.
Dabei gibt es gute Argumente, sie als komplette Lösung zu sehen. Die meisten Bugreports zu veralteten Reads betreffen tatsächlich den Fall nach dem Redirect: Jemand speichert ein Formular, die Seite lädt neu, der alte Wert steht noch da, und der Support bekommt ein Ticket, der Speichern-Button sei kaputt. Die Middleware räumt diese ganze Ticketkategorie mit einer Zeile in `bootstrap/app.php` ab, ohne dass du Domain-Code anfassen musst. Frische an jeder Aufrufstelle von Hand zu markieren verteilt dagegen Connection-Klempnerei über deine Services, und das vergisst man leicht. Fairerweise muss ich sagen: Ein Framework-Default, der den häufigsten Fall abdeckt, ist absolut der Laravel-Weg, und meistens ist das die richtige Entscheidung.
Hier steige ich allerdings aus. Ein Zeitfenster versteckt das Problem, statt es zu lösen. Wenn dein Replica um 03:12 Uhr wegen einer langen Migration neun Sekunden zurückliegt, ist das Zwei-Sekunden-Fenster immer noch zwei Sekunden lang, und der Bug kommt genau dann zurück, wenn du am wenigsten in der Lage bist, darüber nachzudenken. Schlimmer noch: Die Bugs, die richtig Geld kosten, liegen meistens komplett außerhalb der Session. Denk an einen Queue-Job, der direkt nach einer Korrektur einen Lagerbestand liest, oder an einen Webhook-Handler, der direkt nach einer Rollenänderung Berechtigungen prüft. Keine Session heißt dort: kein Schutz.
Deshalb ist mir `usingWritePdo()` wichtiger. Das ältere `onWriteConnection()` hatte eine Lücke, die der Quellartikel gut beschreibt: Die Elternabfrage geht an den Primary, aber die Relation, die du danach daraus lädst, landet womöglich still und leise auf einem Replica. Die neue Callback-Variante legt jeden Read innerhalb des Callbacks auf die Write-Connection und stellt danach die vorherige Einstellung wieder her, auch wenn eine Exception fliegt. Der Schnipsel unten ist also eine Aussage über Absicht. Wer ihn später liest, weiß, dass dieser Lookup den neuesten Schreibvorgang sehen muss und warum er einen Roundtrip zum Primary kostet.
Ja, das ist mehr Code. Gut so. Frische ist eine fachliche Anforderung, die nur zufällig wie Infrastruktur aussieht, und Anforderungen gehören dorthin, wo Menschen sie lesen können. Wenn ein Reviewer sieht, dass die Prüfung des Bestellstatus auf dem Primary läuft und die Dashboard-Sidebar auf einem Replica, dann ist das ein Design, über das man in einem Pull Request streiten kann. Ein Middleware-Fenster ist eine Einstellung, die irgendwer einmal justiert hat und an deren Änderung sich niemand erinnert. (Ein Freund, der Go schreibt, erzählt mir, dass sie das mit einem Context-Flag lösen, das durch elf Funktionssignaturen gereicht wird. Wir bekommen eine Closure. Ich nehme die Closure.)
Der Rest von 13.38 ist solide Aufräumarbeit. `lazy()` und `lazyById()` geben beim Iterieren jetzt den vorherigen Chunk frei. Ein Unique-Job-Lock bleibt nicht mehr hängen, wenn das Dispatchen eines geplanten Jobs fehlschlägt. Aliase von `ofMany()` innerhalb von Closures wurden gefixt. Und `Rule::unique()->where()` akzeptiert jetzt Floats. Nichts davon ändert, wie du Dinge designst, aber gerade der Lock-Fix erklärt mir ein paar Morgen, an denen ich mich gefragt habe, warum der Tagesbericht nicht gelaufen ist.
Also: Schalt die Middleware ein und betrachte sie als Sicherheitsnetz, während du die Reads aufspürst, die wirklich frisch sein müssen, und sie bewusst einpackst. Ich vermute, dass mir viele Teams widersprechen und sagen werden, Reads zu annotieren sei verfrüht, solange Replica-Lag nicht tatsächlich in deinen Logs auftaucht. Darum meine Frage an dich: Wo tut Replica-Lag in deiner Codebase wirklich weh, und passiert das überhaupt jemals in einem Request mit Session?




Kommentare
Noch keine Kommentare — schreib den ersten.
Starte die Diskussion
Kein Konto, kein Passwort nötig — gib einfach deine E-Mail-Adresse ein, wir senden dir einen einmaligen Anmelde-Link. Beim ersten Mal bist du damit automatisch angemeldet.
Deine Bewertung wird nach der Anmeldung automatisch übernommen.
Schau in dein Postfach
Wir haben einen Anmelde-Link an … gesendet. Öffne ihn auf diesem Gerät — dieser Tab meldet dich automatisch an.
Nichts angekommen? Wirf einen Blick in den Spam-Ordner — und markiere die Mail dort als „Kein Spam“, dann landet sie künftig direkt im Postfach.