Get CV
Back to business cases

Symfony

Messenger that charged the card twice

The provider returned 200. The worker died before ack. Redis redelivered. Unique middleware did not cover SIGKILL.

The shop used Symfony Messenger on Redis next to a Laravel catalog on Horizon. Checkout dispatched PayOrder. The handler called the provider, then acked. Docker sent SIGTERM on deploy. One worker had a 200 and no ack. The next consumer charged again.

Messenger unique middleware was on. It serializes overlapping consumers. It does not survive a crash after the HTTP call. Support saw two provider ids for one order. The customer saw two charges.

The problem was a charge without a durable fence. Unique middleware is not a ledger. I needed an insert with unique (order_id, step) before HTTP, then ack. Replay must be empty.

Worker A charges and dies. Worker B redelivers. Both ack. The fence was missing.
Worker A charges and dies. Worker B redelivers. Both ack. The fence was missing.

Insert before HTTP

A table with unique (order_id, step) is the fence. Insert in a transaction before the provider call. On conflict, load the stored provider_ref, skip the charge, ack. Three retries, then the failure transport. Timeout shorter than the Redis reservation.

Claim the key, call the provider, store the reference, then ack. Replay is empty.
Claim the key, call the provider, store the reference, then ack. Replay is empty.

Same Redis, two workers

Horizon and Messenger shared one instance. Prefixes split jobs from cache and sessions. The fence lives in MySQL, not in a Redis SET NX that dies with maxmemory. The Laravel queue on this site uses the same table idea. Symfony was not special. The missing insert was.

What I took from this

Ack after HTTP is a window. SIGTERM in that window is a second charge unless the fence is already in the database.

Unique middleware serializes consumers. It does not survive SIGKILL. A unique row does.

Money steps go in MySQL. Redis is the transport. The two jobs must not share a lock that maxmemory can drop.

Back to business cases