CASE STUDY

MakeMyTrip – Hotel Booking System

20 min read·3,906 words·Beginner

How to use this case study

SDE-2 / Mid

Study the full MakeMyTrip Hotel Booking System case study. Focus on understanding the core components and how they interact. Focus on sections 1-3: requirements, API design, and high-level architecture. Understand the search indexing, availability check, and booking flow.

SDE-3 / Senior

Study the full MakeMyTrip Hotel Booking System case study. Focus on understanding the core components and how they interact. Be ready to discuss the inventory management system, how to prevent double-booking with distributed locks, and the payment saga pattern for booking consistency.

Staff / Principal

Study the full MakeMyTrip Hotel Booking System case study. Focus on understanding the core components and how they interact. Be prepared to discuss the dynamic pricing engine, the search ranking algorithm, and how to handle peak traffic during holiday seasons. Discuss the partner API integration architecture and inventory sync across OTAs.


0) Problem Restatement

Design a hotel booking platform like MakeMyTrip where users can search for hotels, view availability, book rooms, and make payments.


1) Requirements

1.1 Functional

  • Search hotels by location, dates, and filters (price, rating, amenities).
  • View hotel details, photos, reviews, and room availability.
  • Book rooms with real-time availability checks.
  • Payment processing and confirmation.
  • Cancellation and refund handling.
  • Hotel partner dashboard (manage inventory, pricing, bookings).
  • User reviews and ratings.

1.2 Non-Functional

  • High Availability: 99.9% uptime for search and booking.
  • Low Latency: Search results < 500ms, booking confirmation < 2s.
  • Consistency: No double-booking of rooms.
  • Scalability: Handle millions of searches/day, peak traffic during holidays.
  • Reliability: Accurate inventory sync across channels.
  • Data Integrity: Secure payment and booking records.

1.3 Scale Estimates

  • Hotels: 1 million hotels, avg 50 rooms/hotel = 50M rooms.
  • Daily searches: 100M searches/day (~1200 searches/sec avg, 5000/sec peak).
  • Daily bookings: 1M bookings/day (~12 bookings/sec avg, 50/sec peak).
  • Users: 100M registered users.
  • Data: Hotel metadata ~100 GB, booking records ~10 TB/year.


1.4) API Specifications

User-Facing APIs

  • GET /api/hotels/search - Search hotels by location, dates, and filters
  • GET /api/hotels/{hotel_id} - Get detailed hotel information including room types and amenities
  • POST /api/hotels/availability - Check real-time room availability for specific dates
  • POST /api/bookings - Create a new booking with room hold (returns booking_id and payment_url)
  • GET /api/bookings/{booking_id} - Get booking details and status
  • POST /api/bookings/{booking_id}/cancel - Cancel booking and initiate refund

Hotel Partner APIs

  • PUT /api/partner/inventory - Update room inventory, pricing, and availability
  • GET /api/partner/bookings - View bookings for partner's hotels


2) High-Level Architecture

2.1 Overview

  • ClientAPI GatewaySearch ServiceInventory ServiceBooking ServicePayment ServiceNotification Service.
  • Key components: Search with caching, inventory management with locking, async payment processing, and real-time availability updates.

2.2 Flow Diagram

Architecture Diagram

flowchart TB
    %% User Interactions
    U["User - Web/Mobile"] -->|"1. GET /search?location=X&dates=Y-Z"| AG["API Gateway"]
    U -->|"8. POST /book {hotelId, roomType, dates, userId}"| AG
    
    %% Search Flow
    AG -->|"2. search(location, dates, filters)"| SS["Search Service"]
    SS -->|"3. query hotels + availability"| Cache["Redis Cache (Hot Data)"]
    Cache -->|"4. cache miss"| SS
    SS -->|"5. fetch from DB"| HotelDB[(Hotel Metadata DB)]
    SS -->|"6. check availability"| IS["Inventory Service"]
    IS -->|"7. read room counts"| InvDB[(Inventory DB)]
    
    %% Booking Flow
    AG -->|"9. POST /book"| BS["Booking Service"]
    BS -->|"10. holdRooms(hotelId, roomType, checkIn, checkOut)"| IS
    IS -->|"11. Lua: check + decrement all nights"| Redis["Redis (Holds + Per-Night Counters)"]
    IS -.->|"12. sweeper releases expired holds"| Redis
    
    BS -->|"13. initiatePayment(bookingId, amount)"| PS["Payment Service"]
    PS -->|"14. call gateway"| PG["Payment Gateway (Stripe)"]
    PG -->|"15. callback: success/failure"| PS
    
    %% Payment Success Path
    PS -->|"16. confirmPayment(bookingId)"| BS
    BS -->|"17. confirmBooking(bookingId)"| IS
    IS -->|"18. claim hold, decrement all nights (one txn)"| InvDB
    BS -->|"19. INSERT booking record"| BookingDB[(Booking DB)]
    
    %% Async Notifications
    BS -->|"20. emit bookingConfirmed event"| MQ["Message Queue (Kafka)"]
    MQ -->|"21. consume"| NS["Notification Service"]
    NS -->|"22. send confirmation email/SMS"| U
    
    %% Partner Dashboard
    Hotel["Hotel Partner"] -.->|"update inventory/pricing"| PD["Partner Dashboard API"]
    PD -.->|"UPDATE inventory"| IS
    IS -.->|"invalidate cache"| Cache
    
    %% Analytics
    MQ -.->|"consume for analytics"| Analytics["Analytics Service"]
    
    %% Styling
    classDef userFlow fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
    classDef coreService fill:#c8e6c9,stroke:#388e3c,stroke-width:2px;
    classDef storage fill:#fff9c4,stroke:#f57f17,stroke-width:2px;
    classDef async fill:#f8bbd0,stroke:#c2185b,stroke-width:2px;
    
    class U,AG,Hotel userFlow;
    class SS,IS,BS,PS,PD coreService;
    class HotelDB,InvDB,BookingDB,Cache,Redis storage;
    class MQ,NS,Analytics async;

3) Components (what & why)

Client (Web/Mobile)

  • Search interface with filters (location, dates, price, rating, amenities).
  • Hotel detail pages with photos, reviews, room types, and pricing.
  • Booking flow with date selection, room selection, and payment.

API Gateway

  • Authentication, rate limiting, request validation.
  • Routes to appropriate microservices.
  • SSL termination and DDoS protection.

Search Service

  • Responsibilities:
  • Handle search queries with location, dates, and filters.
  • Rank hotels by relevance, price, rating.
  • Integrate with Inventory Service for real-time availability.
  • Optimization:
  • Cache popular searches in Redis (location + dates as key).
  • Use Elasticsearch for full-text search and filtering.
  • Pre-aggregate data for common queries.

Inventory Service (Core)

  • Responsibilities:
  • Manage room availability per hotel, room type, and date.
  • Handle atomic room allocation (hold and confirm).
  • Sync with hotel partner updates.
  • Data Structure:
  • Inventory(hotel_id, room_type, date, available_count, booked_count)
  • Locking Mechanism:
  • Use Redis for temporary holds across all nights of the stay, with a sweeper that releases expired holds.
  • Use one DB transaction (all nights, locked in date order) for final booking confirmation.

Booking Service

  • Responsibilities:
  • Orchestrate booking flow: hold room → payment → confirm.
  • Create booking records with user, hotel, dates, and payment info.
  • Handle cancellations and refunds.
  • State Machine: PENDING → PAYMENT_INITIATED → CONFIRMED / CANCELLED.

Payment Service

  • Responsibilities:
  • Integrate with external payment gateways (Razorpay, Stripe, PayPal etc).
  • Handle async callbacks, retries, and idempotency.
  • Process refunds for cancellations.
  • Security: PCI-DSS compliance, tokenization, encrypted storage.

Hotel Metadata DB

  • Store hotel info: name, location, amenities, photos, reviews, ratings.
  • Read-heavy workload; use read replicas and caching.

Inventory DB

  • Store room availability per hotel, room type, and date.
  • High write load during bookings; use partitioning by hotel_id.

Booking DB

  • Store booking records: user_id, hotel_id, room_type, dates, status, payment_id.
  • Audit trail for all booking state changes.

Notification Service

  • Asynchronous worker consuming from message queue.
  • Send booking confirmations, reminders, and cancellation notifications via email/SMS/push.

Partner Dashboard

  • Hotel partners manage inventory, pricing, and view bookings.
  • Updates trigger cache invalidation for real-time consistency.


4) Data Model (minimal)

Hotel

Hotel(hotel_id, name, location, address, rating, amenities[], photos[], description)

Room Type

RoomType(room_type_id, hotel_id, type_name, capacity, base_price, amenities[])

Inventory (per date)

Inventory(hotel_id, room_type_id, date, total_rooms, available_rooms, price)

Booking

Booking(
  booking_id, 
  user_id, 
  hotel_id, 
  room_type_id, 
  check_in_date, 
  check_out_date, 
  num_rooms,
  total_price,
  status, -- PENDING, CONFIRMED, CANCELLED
  payment_id,
  created_at,
  updated_at
)

User

User(user_id, name, email, phone, preferences[])

Review

Review(review_id, user_id, hotel_id, rating, comment, created_at)

5) Key Flows

5.1 Search Flow

  1. User enters location (e.g., "Goa"), check-in/check-out dates, and filters.
  2. Search Service checks Redis cache for matching query.
  3. On cache miss: Query Elasticsearch for hotels matching location/filters.
  4. For each hotel, check availability via Inventory Service.
  5. Rank results by relevance, price, rating, and return to user.
  6. Cache results in Redis with TTL (5-10 minutes).

5.2 Booking Flow (Happy Path)

  1. User selects hotel, room type, and dates.
  2. Client calls Booking Service: /book {hotel_id, room_type_id, dates, user_id}.
  3. Booking Service calls Inventory Service to hold rooms.
  4. Inventory Service:
  • Atomically reserves the rooms for every night of the stay (check-in up to, but not including, check-out) in Redis — all nights or none.
  • Records the hold with a 10-minute expiry.
  • Returns hold_id to Booking Service.
  • The DB is not touched yet; it is decremented once, at confirmation (Deep Dive A).
5. Booking Service initiates payment via Payment Service.

  1. User completes payment on gateway.
  2. Payment Gateway calls callback URL with success status.
  3. Payment Service verifies callback and notifies Booking Service.
  4. Booking Service confirms booking:
  • Calls Inventory Service to finalize (remove hold, persist booking).
  • Inserts booking record in Booking DB.
10. Emit booking confirmation event to message queue.

  1. Notification Service sends confirmation email/SMS to user.

5.3 Failure Handling

  • Payment Timeout: A sweeper finds holds past their expiry and adds the rooms back to each night's counter. (A plain Redis key TTL is not enough on its own: when a hold key expires, nothing gives the decremented rooms back.)
  • Payment Failure: Mark booking as CANCELLED, release the hold immediately (same release script the sweeper uses).
  • Payment succeeds after the hold expired: Try to re-reserve the same rooms; if they are gone, auto-refund and tell the user. Extending the hold when the user reaches the payment page makes this rare.
  • Double-Booking Prevention: The Redis hold is all-or-nothing across nights; the DB confirm re-checks every night under row locks (or optimistic concurrency with a version field).

5.4 Cancellation Flow

  1. User requests cancellation → Booking Service validates cancellation policy.
  2. If allowed:
  • Update booking status to CANCELLED.
  • Increment available_rooms in Inventory DB.
  • Initiate refund via Payment Service.
  • Invalidate search cache for affected dates.


6) Deep Dive A: Inventory Management & Overbooking Prevention (~10 mins)

Problem

Multiple users may attempt to book the last available room concurrently. We must prevent overbooking while maintaining high throughput.

Challenges

  • Race Condition: Two users check availability (5 rooms available) → both book → oversell.
  • Multi-Night Stays: A 3-night stay needs a room on 3 separate (hotel, room_type, date) rows. Reserving night 1 and 2 but failing on night 3 must not leave rooms stranded — all nights succeed or none do.
  • Abandoned Holds: Users close the tab mid-payment; their rooms must come back automatically.
  • Performance: Locking should not bottleneck high traffic.

Solution: Two-Phase Booking (Redis Hold → DB Confirm)

Combine fast in-memory holds with one authoritative database write at confirmation. The DB count is decremented exactly once — when the booking is confirmed — never at hold time.

Phase 1: Temporary Hold (Redis)

  • When: User clicks "Book Now".
  • Data: One counter per night, avail:{hotel}:{room_type}:{date}, loaded from the DB. Each hold is stored as hold:{hold_id} (which nights, how many rooms) and added to a sorted set holds:expiry scored by its expiry time.
  • Action: A Lua script checks every night of the stay first, and only if all have enough rooms does it decrement them all. Redis runs a Lua script atomically, so no other command can interleave between the check and the decrement.
  • Release: A sweeper runs every few seconds, pulls holds whose expiry has passed from holds:expiry, and runs a release script that adds the rooms back to each night. The same release script is used on payment failure or cancel.
  • Why not just a key TTL? A TTL deletes the hold key, but nothing adds the decremented rooms back to the per-night counters. Expiry has to *do* something, so it is driven by the sweeper.

Flow:
  1. User clicks "Book Now" → Booking Service calls Inventory Service with all nights of the stay
  2. Inventory Service runs the hold script (atomic all-or-nothing check-and-decrement across nights)
  3. If successful: return hold_id and expiry to the user
  4. If failed: Return "Room unavailable" error
  5. User proceeds to payment page (has 10 minutes to complete; reaching the payment page can extend the hold once)

Phase 2: Confirm Booking (DB)

  • When: Payment succeeds (payment gateway callback received).
  • Claim the hold: Atomically remove hold_id from holds:expiry (ZREM returns 1 only for the first caller). If it returns 0, the sweeper already released it → try to re-reserve; if that fails, refund.
  • Persist: One DB transaction locks all night rows for the stay in date order (a fixed lock order prevents deadlocks between overlapping stays), re-checks availability, decrements each night, and inserts the booking.

Flow:
  1. Payment gateway sends success callback → Payment Service → Booking Service
  2. Booking Service claims the hold in Redis (ZREM holds:expiry hold_id)
  3. BEGIN; SELECT ... FOR UPDATE on all nights, ordered by date
  4. If every night still has rooms: decrement each night, insert booking record, COMMIT
  5. If any night is short (DB and Redis drifted — rare): ROLLBACK, release the Redis hold, refund payment

Why Two Phases?
  • Phase 1 (Redis): Fast hold for user experience (no user wants to wait on DB row locks during room selection)
  • Phase 2 (DB): Final pessimistic lock for correctness (ensures money and inventory are consistent)
  • Separation of concerns: Redis handles ephemeral holds, DB holds the permanent record. A reconciliation job periodically rebuilds Redis counters from the DB (total − booked − active holds) to repair drift.

Two-Phase Booking Flow Diagram

Architecture Diagram

sequenceDiagram
    participant User
    participant BookingService
    participant InventoryService
    participant Redis
    participant DB
    participant PaymentGateway
    
    Note over User,PaymentGateway: PHASE 1: Temporary Hold (Redis)
    User->>BookingService: 1. Click "Book Now"
    BookingService->>InventoryService: 2. holdRooms(hotel_id, room_type, check_in, check_out, n)
    InventoryService->>Redis: 3. Lua: check ALL nights, then decrement ALL nights
    
    alt Every night available
        Redis-->>InventoryService: 4. Success, hold_id added to holds:expiry (now + 10 min)
        InventoryService-->>BookingService: 5. Return hold_id
        BookingService-->>User: 6. Show payment page
    else Any night sold out
        Redis-->>InventoryService: 4. Failure (nothing decremented)
        InventoryService-->>BookingService: 5. Error: Room unavailable
        BookingService-->>User: 6. Show "Sold Out"
    end
    
    Note over User,PaymentGateway: User has 10 mins to pay, a sweeper releases expired holds
    User->>PaymentGateway: 7. Complete payment
    PaymentGateway->>BookingService: 8. Callback: Payment Success
    
    Note over User,PaymentGateway: PHASE 2: Confirm Booking (DB)
    BookingService->>Redis: 9. ZREM holds:expiry hold_id (claim hold)
    BookingService->>DB: 10. BEGIN, SELECT ... FOR UPDATE (all nights, date order)
    
    alt Every night still available in DB
        BookingService->>DB: 11. UPDATE each night: available_rooms - n
        BookingService->>DB: 12. INSERT booking record
        BookingService->>DB: 13. COMMIT
        BookingService->>User: 14. Send confirmation (email/SMS)
    else Hold already expired or DB short (edge case)
        BookingService->>DB: 11. ROLLBACK
        BookingService->>PaymentGateway: 12. Initiate refund
        BookingService->>User: 13. Booking failed, refund initiated
    end
SQL Example (confirm a stay of :nights nights):
BEGIN TRANSACTION;

-- Lock every night of the stay, always in date order (prevents deadlocks)
SELECT date, available_rooms FROM Inventory
WHERE hotel_id = :hotel AND room_type_id = :room_type
  AND date >= :check_in AND date < :check_out
ORDER BY date
FOR UPDATE;

UPDATE Inventory
SET available_rooms = available_rooms - :n
WHERE hotel_id = :hotel AND room_type_id = :room_type
  AND date >= :check_in AND date < :check_out
  AND available_rooms >= :n;

-- If rows updated != :nights, some night was short: ROLLBACK and refund
INSERT INTO Booking (...) VALUES (...);
COMMIT;

Lua Script for Atomic Multi-Night Hold (Redis)

-- KEYS: one avail:{hotel}:{room_type}:{date} key per night of the stay
-- ARGV[1] = rooms requested, ARGV[2] = hold_id, ARGV[3] = expiry (epoch seconds)
local n = tonumber(ARGV[1])

-- Pass 1: check every night before touching anything
for i = 1, #KEYS do
  if (tonumber(redis.call('GET', KEYS[i])) or 0) < n then
    return 0  -- some night is sold out; nothing was decremented
  end
end

-- Pass 2: all nights have room, so decrement them all
for i = 1, #KEYS do
  redis.call('DECRBY', KEYS[i], n)
end

-- Record the hold so the sweeper (or a payment failure) can give the rooms back
redis.call('HSET', 'hold:' .. ARGV[2], 'rooms', n, 'nights', table.concat(KEYS, ','))
redis.call('ZADD', 'holds:expiry', ARGV[3], ARGV[2])
return 1

The release script is the mirror image: ZREM holds:expiry hold_id, and only if that returned 1, INCRBY each night by rooms and delete hold:{hold_id}. Checking the ZREM result means a hold is never released twice, and a confirmed hold is never released.

In a Redis Cluster, all of a hotel's keys must live in the same hash slot for a multi-key script, so use a hash tag: avail:{hotel_123}:deluxe:2024-01-15.

Alternative: Optimistic Concurrency Control

Instead of row-level locks, use a version number approach:

Step 1: Read current state
SELECT available_rooms, version 
FROM Inventory 
WHERE hotel_id = 'h123' AND room_type_id = 'r456' AND date = '2024-01-15';

--Returns: available_rooms = 5, version = 42
Step 2: Update with version check
UPDATE Inventory 
SET available_rooms = available_rooms - 1,
    version = version + 1
WHERE hotel_id = 'h123' 
  AND room_type_id = 'r456'
  AND date = '2024-01-15'
  AND version = 42  -- Only succeed if version hasn't changed
  AND available_rooms > 0;

--Check rows affected:
--1 row affected → Success(you got the booking)
--0 rows affected → Conflict(someone else modified it, retry)
Why it prevents race conditions:
  • User A reads version = 42, tries to update WHERE version = 42 → SUCCESS(version now 43)
  • User B reads version = 42, tries to update WHERE version = 42 → FAILS(version is now 43)
  • User B retries with new version

Trade-offs:
  • Pro: No locking overhead, better concurrency
  • Con: Requires retry logic, can have high contention during peak times
  • Best for: Low-conflict scenarios (OCC assumes conflicts are rare)


7) Deep Dive B: Search Optimization & Caching (~8 mins)

Problem

With 1200 searches/sec (peak 5000/sec), querying the DB for every search is expensive and slow.

Solution: Multi-Layer Caching

Layer 1: Redis Cache(Hot Queries)

  • Key : search:{location}:{check_in}:{check_out}:{filters_hash}
  • Value: List of hotel IDs with availability and pricing.
  • TTL: 5-10 minutes (balance freshness vs cache hit rate).
  • Cache Invalidation: On inventory update (hotel partner changes pricing/availability).

  • Purpose: Fast filtering by location, amenities, rating, price range.
  • Indexing: Hotel metadata indexed with geo-coordinates.
  • Query: Geo-proximity search + filters.
  • Update: Near real-time indexing (1-2 sec delay acceptable).

  • Strategy: For popular destinations (e.g., Goa, Dubai), pre-compute hotel lists for common date ranges.
  • Storage: Store in cache with longer TTL (1 hour).
  • Refresh: Periodic background job updates cache.

Caching Architecture

Architecture Diagram

flowchart TD
    User["User Search Request"] --> API["API Gateway"]
    API --> Cache1["L1: Redis Cache"]
    
    Cache1 -->|"cache hit"| Return["Return Cached Results"]
    Cache1 -->|"cache miss"| ES["L2: Elasticsearch"]
    
    ES -->|"fetch hotel IDs"| Inv["Inventory Service (Availability Check)"]
    Inv -->|"check Redis/DB"| Results["Compute Results"]
    Results --> Cache1
    Results --> Return
    
    Hotel["Hotel Partner Update"] --> Invalidate["Cache Invalidation"]
    Invalidate --> Cache1
    
    classDef cache fill:#fff9c4,stroke:#f57f17,stroke-width:2px;
    classDef service fill:#c8e6c9,stroke:#388e3c,stroke-width:2px;
    
    class Cache1,ES cache;
    class Inv,Results service;

Search Ranking Algorithm

score = w1 * relevance_score          // location match
      + w2 * (1 / price)               // price preference
      + w3 * avg_rating                // user reviews
      + w4 * availability_premium      // more rooms = higher score
      + w5 * user_preference_match     // personalization

Personalization

  • Track user's past bookings, searches, and preferences.
  • Boost hotels matching user's preferred amenities (e.g., pool, gym).


8) Deep Dive C: Payment & Idempotency (~7 mins)

Problem

Payment processing is async and may fail, timeout, or be retried. We must ensure exactly-once semantics.

Challenges

  • Double Charging: User clicks "Pay" multiple times.
  • Network Failure: Payment succeeds at gateway but callback fails.
  • Retry Storm: Client retries on timeout, causing duplicate requests.

Solution: Idempotency Key

Implementation

  1. Client generates idempotency key (UUID) on first payment request.
  2. Payment Service checks if key exists in Idempotency Store (Redis/DB).
  • If exists: Return cached response (no-op).
  • If new: Process payment and store result with key.
3. Store state machine per key:

  • INITIATED → PROCESSING → SUCCESS → NOTIFIED / FAILED.

State Machine Diagram

Architecture Diagram

stateDiagram-v2
    [*] --> INITIATED: User clicks Pay
    INITIATED --> PROCESSING: Call Payment Gateway
    PROCESSING --> SUCCESS: Gateway callback (success)
    PROCESSING --> FAILED: Gateway callback (failure)
    PROCESSING --> TIMEOUT: No callback within 60s
    
    SUCCESS --> NOTIFIED: Confirm Booking
    TIMEOUT --> RETRY: Retry (check gateway status)
    RETRY --> SUCCESS
    RETRY --> FAILED
    
    FAILED --> [*]
    NOTIFIED --> [*]

Idempotency Store Schema

{
  "idempotency_key": "uuid-123",
  "booking_id": "booking-456",
  "status": "SUCCESS",
  "payment_id": "pay-789",
  "amount": 5000,
  "created_at": "2023-10-15T10:00:00Z",
  "response": { ... }  // Cached response
}

Webhook Signature Verification

  • Payment gateway signs callback with secret key.
  • Payment Service verifies signature to prevent spoofing.

Refund Handling

  • On cancellation, create refund request with idempotency key.
  • Track refund status separately: PENDING → PROCESSED.


9) Scaling & Performance (~5 mins)

Horizontal Scaling

  • Search Service: Stateless; scale with load balancer.
  • Inventory Service: Partition by hotel_id (consistent hashing).
  • Booking Service: Stateless; scale horizontally.
  • DB: Shard by hotel_id or geography (e.g., US-West, EU, Asia).

Database Partitioning

  • Inventory DB: Shard by hotel_id, so every night of a stay lives on one shard and the confirm transaction never spans shards. (Sharding by (hotel_id, date) would turn every multi-night booking into a distributed transaction.)
  • Booking DB: Shard by booking_id or user_id.
  • Hot Partition Problem: Popular hotels (e.g., Taj Mahal Hotel) get many concurrent *writes*, which replicas don't help with. Keep confirm transactions short, let the Redis hold absorb the contention (only holders reach the DB), and serve availability *reads* from cache.

Caching Strategy

  • Read-Heavy: Hotel metadata, reviews → Cache in CDN and Redis.
  • Write-Heavy: Inventory updates → Write-through cache with invalidation.

CDN for Static Assets

  • Hotel photos, videos → store in S3, serve via CloudFront.

Performance Metrics

  • P99 Search Latency: < 500ms.
  • P99 Booking Latency: < 2s.
  • Cache Hit Rate: > 80% for searches.


10) Failure Modes & Recovery

Database Failure

  • Read Replica Failover: Promote replica to master.
  • Write Failures: Queue writes in Kafka, replay after recovery.

Redis Failure

  • Search Cache: Degrade to Elasticsearch (slower but functional).
  • Inventory Holds: Fallback to DB-only holds (a holds table with expires_at, same all-nights transaction), then rebuild Redis counters from the DB when it recovers.

Payment Gateway Outage

  • Queue Payments: Store in message queue, retry when gateway recovers.
  • Fallback Gateway: Integrate multiple gateways (Stripe + PayPal).

Inventory Sync Issues

  • Reconciliation Job: Periodically rebuild each Redis counter as total − booked − active holds from the DB, and alert on drift.
  • Audit Logs: Track all inventory changes for forensic analysis.

Geo-Redundancy

  • Multi-region deployment (US, EU, Asia).
  • DNS-based routing to nearest region.


11) Trade-offs & Alternatives

Eventually Consistent vs Strongly Consistent Inventory

  • Strong: Use DB locks; slower but no overbooking.
  • Eventual: Use Redis; faster but requires reconciliation.
  • Choice: Hybrid (Redis for holds, DB for confirms).

SQL vs NoSQL

  • SQL: ACID guarantees for bookings and payments.
  • NoSQL: Better for hotel metadata (flexible schema).
  • Choice: SQL for transactional data, NoSQL for metadata.

Synchronous vs Asynchronous Booking

  • Sync: Immediate confirmation, better UX.
  • Async: Decouple payment from booking, better resilience.
  • Choice: Async with optimistic UI updates.


12) Security & Compliance

Payment Security

  • PCI-DSS Compliance: No card data stored; use tokenization.
  • TLS: All communication encrypted.

User Data Protection

  • GDPR: User consent for data collection, right to deletion.
  • Encryption: Encrypt PII (email, phone) at rest.

Rate Limiting

  • Prevent scraping and DDoS attacks.
  • Use API keys for partners.

Fraud Detection

  • Monitor for unusual booking patterns (e.g., 100 bookings in 1 minute).
  • Use CAPTCHA for suspicious activity.


13) Interview Time Allocation (45 min)

  • 5 min: Requirements & scope (functional, non-functional, scale).
  • 10 min: HLD & architecture diagram (components, data flow).
  • 5 min: Data model & key flows (search, booking).
  • 10 min: Deep dive on inventory management & overbooking prevention.
  • 8 min: Deep dive on search optimization & caching.
  • 5 min: Scaling, failure handling, and trade-offs.
  • 2 min: Security, Q&A, and wrap-up.


14) Summary

  • Core Challenges: Inventory consistency (overbooking prevention), search optimization (caching), payment reliability (idempotency).
  • Key Components: Search Service (Elasticsearch + Redis), Inventory Service (Redis holds + DB confirms), Booking Service (orchestration), Payment Service (idempotency + async callbacks).
  • Scaling Strategy: Horizontal scaling, DB partitioning by hotel_id, multi-layer caching (Redis + Elasticsearch + CDN).
  • Correctness: Two-phase booking (Redis hold + DB confirm), row-level locking, idempotency keys for payments.

This design handles millions of searches and bookings per day while ensuring no overbooking and consistent user experience.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →