CV laden
Zurück zu Business Cases

Daten

Locale-Slab statt eines 2-MB-JSON

Drei Locales in einer JSON-Spalte blähten jede Liste auf. Ein gepackter Slab in Memcached halbierte den Worker-Heap.

Der Katalog hatte fünfzigtausend SKUs und drei Locales. Jede Zeile speicherte Titel und Beschreibung als JSON-Objekt mit en, ru und de. Eine Liste mit vierzig Einträgen holte 2 MB JSON aus MySQL, json_decode() baute einen Baum im Worker, Vue SSR bekam denselben Baum noch einmal im Page-Payload.

Der Request wollte Russisch. Der Worker allokierte trotzdem Englisch und Deutsch. Das ist Verschwendung in RSS und Verschwendung in TTFB.

Das Problem war, ein Raster wie einen Document Store zu behandeln. Besucher warteten auf Decode und auf HTML mit zwei ungenutzten Wörterbüchern. Ich brauchte eine Locale auf der Leitung und einen gepackten Wert, den der Worker ohne json_decode() schneidet.

Header mit Offsets, dann drei Locale-Scheiben. PHP liest eine Scheibe mit substr().
Header mit Offsets, dann drei Locale-Scheiben. PHP liest eine Scheibe mit substr().

Der Slab

Ein Slab ist ein Memcached-Wert pro Listenseite. Die ersten Bytes sind ein Header: Magic, Anzahl, Offset-Tabelle. Danach drei gepackte Regionen, eine pro Locale. Jedes Item ist id, slug, price und ein Title mit Längenprefix. Kein JSON. Kein Eloquent. Ein Leser für ru springt zum ru-Offset und läuft vierzig Records.

Den Slab zu bauen ist nicht umsonst, also passiert es nicht im Request. Ein Write in MySQL legt die SKU-ID in ein Redis-Set catalog:dirty. Ein Horizon-Job leert das Set, baut die Seiten mit diesen IDs neu und SET die neuen Slabs. Leser warten nicht auf den Job. Sie behalten den alten Slab, bis der neue da ist.

MySQL ist die Wahrheit. Redis markiert schmutzige IDs. Horizon schreibt Memcached.
MySQL ist die Wahrheit. Redis markiert schmutzige IDs. Horizon schreibt Memcached.

Was Vue SSR nicht mehr schickt

Das SSR-Payload enthielt früher das volle Locale-Objekt jeder Kachel. Nach dem Slab rendert der Server eine Locale und serialisiert nur sie. Der Client hydriert dieselben Strings. Sprachwechsel ist Navigation, kein zweites Wörterbuch im Speicher.

  • Das Listen-Payload fiel von etwa 180 KB HTML auf etwa 40 KB für dieselben vierzig Kacheln.
  • json_decode() verließ den Hot Path.
  • MySQL verlor den JSON-Extract in der Listenabfrage. Der Covering-Index auf id, slug, price reicht.

Der Trick ist nicht exotisch. Es ist dieselbe Idee wie ein Binärprotokoll, angewandt auf einen Katalog, der wie ein Document Store aussah und sich wie eine Liste benahm. JSON ist das Admin-Format. Der öffentliche Pfad liest Bytes.

Was ich daraus mitnehme

Eine Liste ist ein Bereich von Skalaren. JSON ist ein bequemes Schreibformat. Beides auf dem öffentlichen Pfad ist, wie 2 MB ungenutzter Locales in Worker und Dokument landen.

Rebuild vom Request nehmen. Leser behalten den letzten Slab. Schmutzige IDs in Redis sind billiger als json_decode() bei jedem Hit.

Sprachwechsel als Navigation passt dazu, wie Leute Locale wirklich wechseln. Drei Wörterbücher für einen Client-Switch hat niemand bezahlt.

Zurück zu Business Cases