Design Booking System - Follow-up questions - Intuition First, Need more How
Here is solutions for Design Booking System - Follow-up questions.
Main topic:
-
Read-write seperate: elastic search for read, booking for write.
-
Data-model design: design each product code -> to a record -> same product type only: book.
-
Concurrency Booking: optimistic (show availability, handle failures) or pessimistic (real-time checks, slower UX) or distributed lock - when customer starts checkout, immediately reserve with seller API for 5-10 minutes
-
Downtime of Payment System/Third Party Provider: handle downtime of payment system -> using Kafka + Temporal (State Machine) for retry and reverted.
-
Flash Sale: reservation table + waiting room + idempotency.
Book Seller System:

Hotel Booking System:

Ecomerece System:

Flash Sale:

1. Patterns for Design Booking System
-
Using Elasticsearch for read-heavy: fed by CDC for search isolates OLTP from read-heavy geospatial/text queries and keeps search latency low while scaling independently.
-
Sharding the SQL database by hotel_id: localizes booking transactions and inventory updates to a shard.
-
State-machine - Temporal: Idempotency keys, a booking state machine, and event-driven updates via Kafka show good operational hygiene and support safe retries and downstream notifications.
-
The lock should be: hotel_id + day range (D1, D2, D3) that they book.
-
Persimistic Locking: High-concurrency booking
SELECT * FROM tasks WHERE status = 'pending' LIMIT 1 FOR UPDATE SKIP LOCKED;
- Worker 1 runs: SELECT * FROM tasks WHERE status = 'pending' LIMIT 1 FOR UPDATE -> It grabs and locks Task #1.
- Worker 2 runs the same query -> It sees Task #1 is locked. Instead of waiting, it skips Task #1 and instantly grabs Task #2.
-
Optimistic Locking: client need to retry multiple time -> can not use in high concurrency booking.
- Different consistency level in third party data + booking data:
-
Stale data process quickly: 1 - 3 seconds.
-
Worker: webhook + async worker -> call to provier -> update ES.
-
- Update product with muliple sku(Stock Keeping Unit):
- Reservation table
Reservation id = R1 user_id = A sku_id = 100 qty = 1 status = RESERVED expires_at = 13:15- Phase 1: Reserve inventory (very short transaction)
BEGIN; SELECT * FROM inventory WHERE sku_id = 100 FOR UPDATE; available = 3 UPDATE inventory SET available = available - 1, reserved = reserved + 1 WHERE sku_id = 100; INSERT INTO reservation ( order_id, sku_id, qty, expires_at ); COMMIT;- Phase 2: User pays
- Phase 3A: Payment succeeds
BEGIN; UPDATE inventory SET reserved = reserved - 1 WHERE sku_id = 100; UPDATE orders SET status = 'PAID'; COMMIT;- Phase 3B: Payment fails
BEGIN; UPDATE inventory SET available = available + 1, reserved = reserved - 1; UPDATE reservation SET status = 'EXPIRED'; COMMIT;- Phase 4: What if the user never pays?
- A background job runs every minute:
Find expired reservations ↓ available += qty reserved -= qty ↓ Reservation expired -
Separating Reservation and Order services: with a reserve-then-pay flow (with a TTL) aligns with preventing oversell and supports low latency
- Using a Redis Lua script (single-thread): for atomic check-and-reserve on the hot path is a solid way to avoid race conditions in a flash sale
- Instead of every query request you call MySQL, reuse Redis single-thread.
UPDATE inventory SET available = available - 1 WHERE sku_id = 100 AND available > 0;stock:sku:100 reservation:order123local stock = tonumber(redis.call("GET", KEYS[1])) if stock <= 0 then return -1 end redis.call("DECR", KEYS[1]) return stock - 1 - Waiting Room with short‑lived admission tokens: A release loop drains the queue at a rate your backend can handle. Start with a target attempt rate (e.g., 15–20k/sec if you need ~10k/sec successful reservations) and adjust based on p99 latency/error feedback -> a short‑lived, signed admission token (JWT/HMAC) with fields like productId, userId, issuedAt, expiresAt, nonce.