Case studies
Timed holds and lazy expiry
A hold keeps seats for about ten minutes while a buyer pays. Storing the expiry time on each row lets seats free themselves at claim time, without a cleanup job.
A buyer needs a few minutes to pay, so seats must be held, not sold yet. A hold is a temporary claim with an expiry time. Each seat moves through three states: available, held and booked. A held seat whose hold has expired goes back to available, and nobody needs to tell it to.
Concept
Before you start
- State machine: a set of states and the moves allowed between them. Here:
available → held → booked, withheld → availableon expiry. - TTL (time to live): how long a claim stays valid before it expires.
The big idea
Think of a restaurant table reserved for ten minutes with a note reading "held until 7:10". Once 7:10 passes, anyone can take the table. The host does not have to walk the room first to check.
The picture breaks for display. The board showing which tables are free still needs updating, and that is a separate job that does not affect who gets the table.
Walking through it
The claim checks expiry in the same statement that takes the seat. A seat is claimable if it is available, or if it is held and its hold has passed:
UPDATE seats
SET status = 'held',
hold_id = $hold_id,
holder_user_id = $user,
hold_until = now() + interval '10 minutes'
WHERE event_id = $1
AND seat_id = ANY($seat_ids)
AND (status = 'available' OR (status = 'held' AND hold_until < now()));
-- Require the row count to equal the number of seats requestedHere is one seat over time:
10:00:00 A holds 14C, hold_until = 10:10:00
10:04:00 B claims 14C → hold not expired → 0 rows, refused
10:10:01 B claims 14C → hold expired → 1 row, B holds itNo job ran at 10:10. The expired hold simply stopped counting. A sweeper can still tidy stale hold records for the seat map, but correctness does not depend on it.
Redis and the database
Some teams keep active holds in Redis for speed, using a key with a TTL and an atomic SET … NX. Redis absorbs claim storms in memory, and the database handles the final commit.
Trade-off
A database-only hold is simpler and strongly consistent, but it sends every claim to the primary. Redis plus the database absorbs spikes. The cost is that a Redis flush forgets holds. Treat a missing Redis hold as expired, then check the database. The unique constraint on sold seats is the final backstop either way.
Use the database clock for expiry comparisons. Application servers can disagree with each other by enough to make holds expire early or late.
Common mistakes
- Freeing seats only with a cron job. Between runs, a stale hold blocks a valid claim. Check expiry at claim time.
- Keeping holds only in Redis. A flush erases every hold, so the database must know them too.
- Skipping the expiry check at booking time. A buyer whose hold expired mid-payment must not be able to book.
- Comparing times across machines. Use one clock, the database's.
Check yourself
1. A hold expires at 10:10. Is the seat claimable at 10:10:30 if nothing swept it?
Answer: Yes. The claim checks that hold_until has passed, so the seat is free without a sweeper.
2. Why keep the unique constraint on sold seats if holds already exist?
Answer: It is the last line of defence. Even if holds or the cache fail, the database refuses a second sale of the same seat.
Key takeaways
- Model seats as
available,heldandbooked, with an expiry on held seats. - Check expiry when claiming a seat, so inventory frees itself.
- Redis can speed up holds, but the database has the final say.
- Use the database clock for every expiry comparison.
Further learning
Next, see how a waiting room controls how many buyers reach the hold path in the virtual waiting room.
Related
Case study: ticket booking
Ticketmaster-style inventory under extreme contention, with holds that expire, a virtual waiting room and atomic seat commits so one seat is never sold twice.
Check-then-act races
Why reading a seat's status and then writing it back can sell the same seat twice, and how one conditional UPDATE turns the check and the claim into a single atomic step.
Virtual waiting room
A waiting room admits buyers at a rate the booking system can handle. Signed, short-lived tokens keep bots and forged requests out of the hold path.