Architecture patterns
Contention and holds
When many clients change one scarce unit, such as a seat or a coupon, use a conditional update with a time-limited hold, plus a waiting room. Locks are simple to explain, but they fail under a sudden spike.
Many clients may want the same unit at the same moment. It might be a seat, a coupon, or the last item of a product. The pattern that scales best is a conditional update, a hold that expires, and admission control for the crowd. Locks are the first idea people reach for, and they tend to break under a sudden spike.
Concept
Before you start. A conditional update changes a row only if it still matches a condition, such as status = 'FREE'. A hold is a temporary claim on a unit, with an expiry time. A waiting room lets only a set number of users into the purchase flow at a time.
The big idea
Think of a restaurant that marks a table as "reserved for 10 minutes". The reservation is a hold. If the guest does not arrive, the table goes back on sale. The hold protects the table while it is being decided.
The analogy breaks if the reservation never expires. A table held forever is worse than no reservation at all.
Walking through it
Claim seat 14C with a conditional update. Only one client can succeed.
UPDATE seats
SET status = 'HELD',
held_by = 'user-42',
hold_expires_at = now() + interval '10 minutes'
WHERE id = '14C' AND status = 'FREE';Output:
UPDATE 1 hold granted for user-42
UPDATE 0 someone else holds 14C; show it as unavailable or try another seatThe flow continues in three steps:
- Hold the seat for 10 minutes, as above.
- Take payment. Confirm the seat only if the hold is still valid.
- A sweeper resets expired holds to
FREE, so the seat goes back on sale.
The waiting room sits in front of step 1. It admits a fixed number of users per second, so the database never sees the full spike.
Trade-off
Locks are easy to explain, but they serialize every request on one row, and they die under onsale load. A conditional update, a hold with a time-to-live, and admission control scale much further.
Common mistakes
- Using
SELECT FOR UPDATEon every seat in a flash sale. Requests queue on the locks. - Holds with no expiry. A user who closes the tab keeps the seat forever.
- Confirming without checking the hold. A payment can finish after the hold expired and someone else took the seat.
Check yourself
- What does
UPDATE 0tell the client? Another client changed the seat first. The client should show it as unavailable, or pick another seat. - What breaks if holds have no time limit? Abandoned holds keep units locked, so real buyers are turned away.
Key takeaways
- Use conditional updates on scarce units, not long-held locks.
- A hold with a time limit protects a unit while a user decides.
- A waiting room caps how many users contend at once.
- Confirm a hold before taking payment, and handle an expired hold.
Further learning
The ticket booking case study applies this pattern end to end.
For payment-side safety, see the payment system case study.
Next, see how to keep two systems in agreement after a hold, in multi-step consistency.
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.
Case study: payment system
Moving money correctly, with payment intents, idempotency at every hop, a double-entry ledger, webhooks and reconciliation when the network lies. Start here for the payments map.
Case study: ride-sharing
Uber-style dispatch: streaming driver locations into a geo index, matching under contention, trip state and geographic sharding. Start here for the ride-sharing map.