Designing a Ticket Booking System
Selling 10,000 tickets to 10,000,000 users in 5 minutes is one of the hardest concurrency problems in engineering.
1. Requirements
Functional
- Search: View available seats.
- Select: Click a seat to temporarily hold it.
- Pay: Complete purchase within 5 minutes.
Non-Functional
- Fairness: First Come, First Served (FCFS).
- No Double Booking: Crucial. Two people cannot buy Seat 1A.
- Scalability: Handle 1M+ Requests Per Second (RPS) during "The Drop".
2. The Core Problem: Race Conditions
Imagine a SQL table seats with status='AVAILABLE'.
- User A checks Seat 1A:
SELECT status ...-> Returns 'AVAILABLE'. - User B checks Seat 1A:
SELECT status ...-> Returns 'AVAILABLE'. - User A Buys:
UPDATE seats SET status='SOLD' WHERE id='1A'. - User B Buys:
UPDATE seats SET status='SOLD' WHERE id='1A'.
Both users think they own the ticket. User B overwrites User A. This is a Default (Read Committed) isolation failure.
3. Database Locking Strategies
Solution 1: Pessimistic Locking (SELECT FOR UPDATE)
We lock the row when reading it.
START TRANSACTION;SELECT * FROM seats WHERE id='1A' FOR UPDATE;(User B blocks here).UPDATE seats ...COMMIT;- Problem: Terrible performance. Database locks are expensive. If User A's internet dies, the lock is held until timeout.
Solution 2: Optimistic Locking (Versioning) - Recommended
We add a version column to the row.
- User A reads Seat 1A (Version 1).
- User B reads Seat 1A (Version 1).
- User A writes:
UPDATE seats SET status='SOLD', version=2 WHERE id='1A' AND version=1.- Result: 1 Row Affected. Success.
- User B writes:
UPDATE seats SET status='SOLD', version=2 WHERE id='1A' AND version=1.- Result: 0 Rows Affected. (DB sees version is now 2, not 1).
- Action: Tell User B "Sorry, someone beat you."
4. Architecture: The Booking Flow
We cannot let 10 Million users hit the Postgres DB directly.
Step 1: The Waiting Room (Virtual Queue)
Before you even see seats, you enter a queue.
- Tech: AWS Lambda + Redis Sorted Set / Kafka.
- Logic: Only allow 500 users per second to enter the "Booking Area".
- Benefit: Protects the database from "The Thundering Herd".
Step 2: Temporary Hold (Redis)
When a user clicks "Seat 1A", we don't write to SQL yet. We write to Redis.
SET seat_1A_lock user_id NX EX 300- NX: Only set if not exists (Atomic lock).
- EX 300: Expire in 300 seconds (5 minutes).
- If Redis returns
OK, show "Time Remaining: 5:00" timer. - If Redis returns
NULL, show "Seat taken".
Step 3: Payment & Final Commitment
User enters credit card info.
- Payment Service: Charge card. Success.
- Booking Service: Now we persist to SQL.
UPDATE seats SET status='SOLD', owner='UserA' WHERE id='1A'.
- Cleanup: Delete Redis key (or let it expire).
5. Handling Data Consistency
What if Redis says "Locked" but the SQL Database crashes?
- Reconciliation Worker: A background job checks:
- "Is this seat locked in Redis for > 5 mins?" -> Release it.
- "Is this seat 'SOLD' in SQL but 'Open' in Redis?" -> Sync them.
6. DB Partitioning (Sharding)
A single Postgres instance can't handle 100k writes/sec.
- Shard by Event ID: All seats for "Taylor Swift NYC" live on Shard 1.
- Risks: "Hot Shard" problem. If everyone wants Taylor Swift, Shard 1 melts while Shard 2 is empty.
Summary
- Queue: Throttle users before they reach the DB.
- Fast Lock: Use Redis
SET NXfor the 5-minute hold. - Safe Write: Use Optimistic Locking (Version #) for the final SQL commit.
About ScaleWiki
ScaleWiki is an interactive educational platform dedicated to demystifying distributed systems, software architecture, and system design. Our mission is to provide high-quality, technically accurate resources for software engineers preparing for interviews or solving complex scaling challenges in production.
Read more about our Editorial Guidelines & Authorship.
Educational Disclaimer: The architectural patterns and system designs discussed in this article are based on common industry practices, technical whitepapers, and public engineering blogs. Actual implementations in enterprise environments may vary significantly based on specific product requirements, legacy constraints, and evolving technologies.
Related Articles
System Design: Instagram News Feed
Designing a scalable social feed. Fan-out on Write vs Fan-out on Read, and solving the Justin Bieber problem.
System Design: URL Shortener (Bit.ly)
Designing a high-read, heavy-scale service like Bit.ly. Deep dive into ID generation (Base62 vs UUID) and Redirection mechanics.
CAP Theorem
Consistency, Availability, Partition Tolerance. Why you can only pick two in distributed systems, and how real databases like MongoDB, Cassandra, and DynamoDB make the trade-off.