How do you stop two people buying the last ticket?
By Saksham Jain ·
Decrement the ticket count inside a short database transaction that holds a pessimistic row lock, and reserve each ticket in Redis with a time-to-live. The second buyer's write waits milliseconds behind the first, then sees sold out. Abandoned checkouts release their reservation automatically when the TTL expires.
From the production case study FindYourVibe.
Why does a read-then-write on the ticket count oversell?
Two requests can read the same remaining count of one, both pass the check, and both write a sale. The count was correct when read and stale when written, so one ticket is sold twice. Any read-then-write on shared inventory has this race unless the write is made atomic or locked.
A plain optimistic retry fixes the correctness problem but starts thrashing once a popular event's last few tickets are hit by a burst of checkouts: every losing buyer's client has to retry the whole checkout, not just a write.
Why use a pessimistic row lock for the last few tickets?
A pessimistic lock makes the second writer wait briefly instead of failing and retrying blind. For one hot row that many buyers want at once, a few milliseconds of waiting is cheaper than whole-checkout retries. The lock is held only for the short transaction that decrements the ticket count.
How does a Redis reservation hold keep abandoned checkouts from blocking inventory?
When a buyer starts checkout, reserve the ticket in Redis with a TTL. If they pay, the reservation converts to a sale. If they leave, the key expires and the ticket returns to inventory automatically. Nobody can hold a ticket hostage by opening a checkout tab and walking away.
What happens when the payment webhook arrives twice?
Nothing extra. The webhook handler is idempotent, keyed on order ID, and its only job is to publish a payment-confirmed event to RabbitMQ. A retried delivery finds the order already handled, so a ticket is never issued twice. A downstream consumer generates the ticket and sends the confirmation.
The same pattern is covered step by step in the note on idempotent webhooks.
Does this hold up under load?
In testing on FindYourVibe, checkout stayed correct under 300+ concurrent buyers on a single event drop, without overselling a ticket. The design choices that made that hold are the row lock on the last tickets, the Redis hold with a TTL, and the idempotent webhook feeding a queue.
- Pessimistic row lock on the last few tickets of an event
- Redis reservation hold with a TTL, so no overselling
- RabbitMQ decouples ticket issuance from the webhook response
- Idempotent payment webhook keyed on order ID