Symfony-Messenger-Ack ist kein Zahlungszaun
Redis-Transport liefert nach einem Crash erneut. Horizon hat dieselbe Regel. Ack nach dem Provider-Call ist die zweite Abbuchung.
Eine Karte wurde zweimal belastet auf einem Symfony-Host, der schon Horizon auf Laravel fuhr. Laravel Horizon und Symfony Messenger sitzen in diesem Stack beide auf Redis. Lieferung mindestens einmal. Ein php-fpm-Worker im Queue-Container starb nach 200 vom Provider und vor dem Ack. Redis gab die Nachricht dem nächsten. Der Handler hatte keinen Zaun. Die Karte wurde erneut belastet.
Nutzer sahen zwei Abrechnungen. Das Worker-Log zeigte einen Erfolg und einen Retry. Ack nach dem HTTP-Call ist, wie die zweite Abbuchung wartet. unique-Middleware ist kein Zaun. Sie deckt Überlappung zweier laufender Worker. Nicht den Retry nach SIGKILL.
Der Zaun ist eine Tabelle mit Unique-Key aus Order-ID plus Schrittname. Insert vor dem HTTP-Call. Trifft das Insert unique, return und ack.
Ack zuletzt, Claim zuerst
Der lokale Insert sitzt in einer Transaktion. Der Provider-Call nach dem Claim. Die Provider-Referenz liegt neben dem Key. Ein Replay nutzt diese ID. Messenger-Retries sind begrenzt. Drei Versuche, dann Failure-Transport und Alert. Das Job-Timeout ist kürzer als die Redis-Reservation.
Dasselbe Muster steht in der Horizon-Notiz auf dieser Site. Symfony ist kein anderes Produktproblem. Es ist dieselbe Redis-Liste mit einem anderen Worker-Binary.
Ich habe Auto-Ack auf dem Redis-Transport mit Ack nach dem Handler verglichen. Auto-Ack vor dem HTTP-Call verliert die Nachricht, wenn der Prozess mitten im Call stirbt. Ack nach dem Call ohne Zaun verdoppelt den Call. Zuerst Claim in MySQL, dann HTTP, Referenz speichern, dann ack. Der Failure-Transport darf HTTP nicht erneut fahren, ohne unique zu treffen.
Was vom Bus bleibt
Login, CSRF und /api/user bleiben synchron. Eine Nachricht, die vor dem HTML fertig sein muss, ist ein langsamer Request mit extra Failure Modes. Kontakt-Mail kann warten. Ein Cache-Rebuild kann warten. Eine Zahlung ohne Zaun nicht.
Queue, Cache und Sessions teilen Redis. Prefixes bleiben getrennt, damit das Evicten eines Listen-Slabs keine reservierte Nachricht kippt. Dieses Prefix gehört in den Handler, nicht in die Logzeile nach dem Vorfall.
Fehlgeschlagene Messenger-Nachrichten wirkten gesund, die Zahlungstabelle hatte zwei Zeilen für einen Schritt. Ich zähle Zaun-Inserts gegen Provider-200. Liegen die 200 höher, lief Ack nach einem Call ohne Unique-Claim.
Was ich daraus mitnehme
Ich messe Provider-200 gegen Unique-Zeilen pro Order-Schritt, nicht Messenger-Throughput. Redis liefert erneut. Der zweite Handler-Lauf muss acken ohne abzubuchen.
unique-Middleware und Ack-nach-HTTP traue ich nicht als Zaun. Zuerst den Key inserten. Provider-ID speichern. Ack zuletzt.
Die Regel, die ich behalte: derselbe Zaun wie Horizon, Prefixes getrennt vom Cache, Login vom Bus. Timeout kürzer als die Redis-Reservation. Drei Versuche, dann Failure-Transport.
