Messenger, der die Karte zweimal belastete
Der Provider lieferte 200. Der Worker starb vor dem Ack. Redis lieferte erneut. Unique-Middleware deckte SIGKILL nicht.
Der Shop nutzte Symfony Messenger auf Redis neben einem Laravel-Katalog auf Horizon. Checkout dispatchte PayOrder. Der Handler rief den Provider, dann ack. Docker schickte SIGTERM beim Deploy. Ein Worker hatte 200 und kein Ack. Der nächste Consumer belastete erneut.
Messenger unique-Middleware war an. Sie serialisiert überlappende Consumer. Sie überlebt keinen Crash nach dem HTTP-Call. Support sah zwei Provider-IDs für eine Order. Der Kunde sah zwei Abbuchungen.
Das Problem war eine Belastung ohne dauerhaften Zaun. Unique-Middleware ist kein Ledger. Ich brauchte ein Insert mit unique (order_id, step) vor HTTP, dann ack. Replay muss leer sein.
Insert vor HTTP
Eine Tabelle mit unique (order_id, step) ist der Zaun. Insert in einer Transaktion vor dem Provider-Call. Bei Konflikt die gespeicherte provider_ref laden, nicht belasten, ack. Drei Retries, dann Failure-Transport. Timeout kürzer als die Redis-Reservation.
Ein Redis, zwei Worker
Horizon und Messenger teilten eine Instanz. Prefixes trennten Jobs von Cache und Sessions. Der Zaun lebt in MySQL, nicht in einem Redis SET NX, das mit maxmemory stirbt. Die Laravel-Queue auf dieser Site nutzt dieselbe Tabellenidee. Symfony war nicht besonders. Der fehlende Insert war es.
Was ich daraus mitnehme
Ack nach HTTP ist ein Fenster. SIGTERM in diesem Fenster ist eine zweite Belastung, wenn der Zaun noch nicht in der Datenbank liegt.
Unique-Middleware serialisiert Consumer. Sie überlebt SIGKILL nicht. Eine Unique-Zeile schon.
Geldschritte liegen in MySQL. Redis ist der Transport. Die zwei Jobs dürfen keinen Lock teilen, den maxmemory werfen kann.
