Problem
Design a ticket booking system for high-demand events (concerts, sports) where the core constraint isn't scale for its own sake — it's guaranteeing a seat is never sold to two different people while a flash-sale crowd hammers the same inventory at once.
Requirements
Functional
- Browse events/venues, view a seat map, select seats, and complete a purchase.
- A seat is never double-booked, even under concurrent requests for the same seat.
- A temporary hold on selected seats while payment completes (a 5–10 minute reservation window).
- Support flash-sale on-sale moments with a large simultaneous traffic spike.
Non-functional
- Strong consistency for seat state, even under concurrency — this is a correctness problem, not just a performance one.
- Stay available during a flash-sale spike rather than falling over at the exact moment it matters most.
- Low-latency seat-map reads despite writes needing strict consistency.
Architecture
- Seat inventory service: owns seat state (available / held / booked) per venue/event, backed by a strongly consistent store (row-level locking, or optimistic concurrency via a version/CAS column).
- Reservation flow: selecting seats acquires a short-lived hold with an expiry, not an immediate booking; an expiry sweeper (or store TTL) releases holds that never convert.
- Waiting room: a virtual queue in front of the booking flow admits users into seat-selection at a controlled rate, so the inventory service never sees more concurrent writes than it can safely serialize.
- Read path: a denormalized, eventually-consistent seat-map cache serves browsing users cheaply; the actual hold/booking write always goes through the strongly consistent inventory service, the sole source of truth.
- Payment: a hold is created first, payment is processed against a provider, and only a successful payment converts the hold into a confirmed booking; a failed or timed-out payment releases it.
Key decisions
- Strong consistency (locking or optimistic-concurrency retry) on the seat write path, not an eventually-consistent design — double-booking is a correctness bug users notice immediately and it costs money and trust directly.
- A hold-then-confirm two-phase flow over immediate booking, since payment latency is unpredictable (redirects, 3DS, provider timeouts) and a seat can't be held open-endedly without a release mechanism.
- Admission control (waiting room) at the edge rather than expecting the strongly-consistent write path to gracefully absorb an unbounded spike — easier to control demand than to make a consistent store elastically scale to it.
Tradeoffs
- The eventually-consistent seat-map cache means a user occasionally sees a seat as available that's gone by the time they click — an accepted UX tradeoff over paying for a strongly-consistent read on every page view.
- The waiting room adds complexity and can frustrate queued users during a real spike, but the alternative is the inventory service falling over entirely at the moment it matters most.
- Hold-expiry windows: too short and legitimate slow payers lose their seat; too long and inventory sits needlessly locked away from other buyers during a high-demand sale.
Failure modes
- Inventory service partition/failure: the system should fail closed (refuse new holds) rather than risk double-booking by degrading to looser consistency.
- Payment timeout after a hold is created needs an async reconciliation job that checks payment status and confirms or releases the hold, rather than trusting only the synchronous request path.
- A waiting-room admission bug (admits too fast) still needs the inventory service's per-seat locking as the final correctness guardrail — defense-in-depth here is cheap relative to a double-booking incident.
Capacity estimates
- A flash sale for a major venue (say 20K seats) can see hundreds of thousands of concurrent hopeful buyers in the opening seconds — the admission-control layer is sized to that demand/supply mismatch, not the inventory service.
- Seat-inventory writes are bounded by the number of seats, not the number of requesting users — a 20K-seat venue is at most 20K hold/booking transactions however many people are trying to get them.
- The seat-map read path has to absorb the full request volume (hundreds of thousands of reads/sec at peak), which is exactly why it's served from cache rather than the consistent store.
What I'd change at 10x scale
- Push admission control further out to the edge/CDN (token-bucket admission at an edge function) rather than the origin, so origin infrastructure never absorbs the full brunt of a spike before shedding load.
- Explicit per-venue sharding of the inventory service, so one venue's flash sale can't contend with unrelated venues' normal traffic on shared infrastructure.
- Predictive pre-scaling ahead of a known on-sale moment, rather than purely reactive autoscaling — flash-sale traffic is a scheduled event, not an unpredictable one.