SYSTEM_DESIGN
System Design: Event Booking Platform
Design a general-purpose event booking platform supporting conferences, festivals, workshops, and meetups. Covers seat selection, waitlists, payment, refunds, and organizer dashboards with emphasis on double-booking prevention.
Requirements
Functional Requirements:
- Event organizers create events with capacity limits, ticket tiers, and pricing
- Attendees browse, search, and register for events with seat or general admission tickets
- Support waitlists: when capacity is full, users join a waitlist and are notified on cancellations
- Payment processing with support for refunds within a configurable window
- Organizer dashboard with real-time attendee counts, check-in status, and revenue reports
- QR code ticket generation and mobile check-in scanning at the venue
Non-Functional Requirements:
- Prevent double-booking under concurrent purchase attempts
- Ticket purchase confirmed within 3 seconds for 95th percentile
- Support up to 100k ticket purchases per hour during peak flash sales
- 99.9% uptime; event organizers depend on the platform for business-critical operations
- Idempotent payment processing: retries never cause duplicate charges
Scale Estimation
For a mid-size platform: 10M events/year, average 500 attendees each = 5B registrations/year or ~158/second average. Peak: a popular conference selling 50k tickets in 1 hour = ~14/second sustained but with a burst at sale open of 50k simultaneous connections. Organizer dashboard queries: 100k active organizers × 5 queries/hour = ~139 queries/second. Check-in scanning: 50k attendees × 2 scans each (entry, session) = 100k scans over 4 hours = ~7 scans/second per large event.
High-Level Architecture
The platform is structured around an Event Service, a Registration Service, a Waitlist Service, and a Payment Service. The Event Service manages event metadata, ticket tiers, and capacity counters. The Registration Service handles the purchase flow with atomic inventory decrement and idempotent payment integration. The Waitlist Service manages ordered queues of interested attendees and processes automatic promotions when registrations are cancelled.
Capacity enforcement uses an optimistic concurrency approach backed by Redis atomic counters. Each ticket tier has a Redis counter initialized to its capacity. A purchase attempt atomically decrements the counter: if the result is ≥ 0, the slot is claimed; if < 0, the counter is incremented back and the user is offered the waitlist. This avoids database row locks for the hot path while using PostgreSQL as the durable inventory source, reconciled by a periodic sync job.
The check-in system is designed for offline tolerance: QR codes contain a cryptographically signed payload that venues can validate locally even without connectivity. Online check-in additionally marks the ticket as used in the database to prevent replay. The two modes provide resilience against venue WiFi failures — a common real-world scenario.
Core Components
Registration & Inventory Service
Handles the booking transaction: validate event availability → claim inventory slot (Redis decrement) → create a pending order record → call Payment Service → on success, mark order confirmed and emit a ticket generation event; on failure, release the inventory slot. The inventory claim and order creation are wrapped in a saga pattern with a compensating transaction (slot release) if the payment step fails. Order creation is idempotent on (user_id, event_id, tier_id, idempotency_key).
Waitlist Service
Maintains an ordered queue per event tier using a Redis Sorted Set (scored by join timestamp). On a registration cancellation, a Kafka event triggers the Waitlist Service to promote the front of the queue: send a notification with a time-limited purchase link (valid for 30 minutes). If the promoted user doesn't complete the purchase, the next in queue is notified. The waitlist state is durably backed by PostgreSQL; Redis is used for fast position lookups and real-time updates.
QR Code & Check-in Service
Generates tickets as signed JWT payloads (ticket_id, event_id, user_id, tier, signed with ECDSA key). QR codes encode the JWT string. The check-in app (native mobile) caches the event's signing public key and validates QR codes locally. Online check-in additionally calls the Check-in API to mark the ticket as used (preventing replay). A Redis Set per event tracks used ticket IDs for fast duplicate detection. The Check-in Service reports real-time attendance counts to the organizer dashboard via WebSocket push.
Database Design
Events: event_id UUID, organizer_id, title, description, venue, starts_at, status ENUM. Ticket tiers: tier_id, event_id, name, price, capacity, sold_count. The sold_count column is the durable inventory counter, updated by the Registration Service. A check constraint sold_count <= capacity provides a database-level guard against overselling if the Redis layer has inconsistency.
Orders: order_id UUID, user_id, event_id, tier_id, status ENUM, idempotency_key UNIQUE, created_at, paid_at. Tickets: ticket_id UUID, order_id, seat_label, qr_payload, checked_in_at. Refunds: refund_id, order_id, amount, reason, initiated_at, processor_refund_id. Check-in events: an append-only checkin_log for audit and real-time dashboard aggregation.
API Design
POST /api/v1/events/{eventId}/register — body: {tier_id, quantity, idempotency_key}; returns {order_id, status} or {error: "SOLD_OUT", waitlist_position}.
POST /api/v1/orders/{orderId}/pay — completes payment; idempotent; returns {ticket_ids, qr_codes[]}.
POST /api/v1/orders/{orderId}/refund — initiates refund if within refund window.
POST /api/v1/checkin/scan — validates and records a ticket scan; returns {valid, attendee_name, tier, already_used}.
Scaling & Bottlenecks
Inventory contention during flash sales is the primary bottleneck. Redis atomic decrements handle 100k+ ops/second per node, sufficient for simultaneous ticket rushes. The PostgreSQL sold_count is updated asynchronously in batches by a reconciliation worker rather than on every sale, reducing write contention. A final reconciliation runs before and after each sales window to ensure consistency.
The organizer dashboard generates aggregation queries over large event datasets. A separate read replica handles these queries, and OLAP-style summary tables are materialized on a 1-minute schedule for real-time dashboard metrics. This prevents dashboard queries from competing with the booking transaction path on the primary database.
Key Trade-offs
- Redis-backed vs. database-backed inventory: Redis decrement gives sub-millisecond inventory claims at scale but adds operational complexity and a consistency gap; database-backed with advisory locks is simpler but serializes concurrent purchases.
- Saga vs. 2PC for booking transaction: Saga with compensating transactions is resilient to partial failures and scales across services, but requires careful rollback logic; 2PC is simpler to reason about but creates cross-service blocking.
- Signed QR vs. online-only tickets: Signed QR codes allow offline check-in resilience but require key rotation planning; online-only tickets are simpler but fail when venue connectivity fails.
- General admission vs. assigned seating: General admission (counter-based) is simpler to implement; assigned seating requires a seat map model and per-seat state, dramatically increasing reservation complexity.
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.