Case Study: Design a Hotel Reservation System

“Hotel reservation system giống như một sân bay quốc tế — hàng triệu người cùng muốn đặt chỗ trên cùng một chuyến bay, nhưng mỗi chuyến chỉ có giới hạn chỗ ngồi. Nếu bạn quá số ghế, hành khách sẽ bị ‘bumped’ và uy tín của hãng bay mất vĩnh viễn.”

Tags: system-design hotel-reservation concurrency-control overbooking inventory-management alex-xu-vol2 case-study Student: Hieu Prerequisite: Tuan-02-Back-of-the-envelope · Tuan-07-Database-Sharding-Replication · Tuan-08-Message-Queue Lien quan: Tuan-11-Microservices-Pattern · Tuan-14-AuthN-AuthZ-Security · Tuan-15-Data-Security-Encryption · Case-Design-Payment-System Reference: Alex Xu, System Design Interview Volume 2 — Chapter 6: Hotel Reservation System


1. Context & Why — Tại sao Hotel Reservation System quan trọng?

1.1 Analogy: Hệ thống đặt phòng khách sạn như Booking.com / Agoda

Hiếu, bạn tưởng tượng mình đang xây dựng hệ thống cho Booking.com hoặc Agoda. Mỗi ngày có hàng triệu người trên toàn thế giới truy cập để tìm phòng khách sạn. Họ search theo thành phố, ngày check-in/check-out, số người, rồi chọn phòng và đặt.

Vấn đề là: khách sạn “Mường Thanh Grand Đà Nẵng” chỉ có 50 phòng Deluxe. Khi có 200 người cùng muốn đặt phòng Deluxe cho đêm 31/12 (Giao thừa), hệ thống phải:

  1. Hiển thị chính xác số phòng còn trống — không được hiển thị “còn phòng” khi đã hết
  2. Đảm bảo không bán quá (overbooking) — 50 phòng thì chỉ cho 50 người đặt (hoặc nhiều hơn 1 chút nếu có overbooking strategy)
  3. Xử lý đồng thời — 200 người click “Đặt ngay” cùng lúc, chỉ 50 người thành công
  4. Không charge 2 lần — người dùng click retry vì mạng chậm, không được tạo 2 booking
  5. Tích hợp thanh toán — sau khi đặt phòng, phải thu tiền hoặc giữ thẻ (authorization hold)

1.2 Tại sao đây là bài toán khó?

Thách thứcGiải thích
Concurrency controlHàng ngàn người cùng đặt phòng một thời điểm — làm sao đảm bảo không bán quá số phòng?
Inventory accuracySố phòng phải chính xác real-time. Hiển thị “còn 2 phòng” nhưng thực tế đã hết = trải nghiệm tệ
Double booking preventionNgười dùng click đặt 2 lần, mạng chậm retry — không được tạo 2 reservation
Peak trafficDịp lễ Tết, Noel, Black Friday — traffic tăng 10-50x so với ngày thường
Cross-service consistencyĐặt phòng thành công nhưng payment fail → phải rollback inventory
Search performanceTìm khách sạn theo location, giá, rating, amenities — dataset lớn, query phức tạp
Overbooking as business decisionAirlines overbooking 5-10% là bình thường. Khách sạn cũng vậy. Phải configurable

1.3 Tại sao Backend Dev cần hiểu Hotel Reservation?

Lý doGiải thích
Concurrency là core skillHiểu concurrency control (pessimistic/optimistic locking) giúp bạn xử lý bất kỳ bài toán nào có shared resource
Inventory management patternE-commerce (tồn kho), ticketing (ghế ngồi), airline (chỗ) — tất cả đều dùng pattern tương tự
Real-world distributed transactionĐặt phòng + thanh toán + cập nhật inventory = distributed transaction điển hình
Read-heavy + Write-criticalSearch (read) nhiều gấp bội, nhưng reservation (write) phải chính xác 100%
Interview favoriteĐây là bài thường gặp trong system design interview vì nó bao phủ nhiều concept

Aha Moment: Hotel reservation không chỉ là “CRUD rooms và bookings”. Core problem là concurrency control trên shared inventory — và cách giải quyết nó ảnh hưởng đến toàn bộ architecture.


Step 1 — Requirements

1.1 Functional Requirements

RequirementMô tảPriority
Search hotelsTìm khách sạn theo location, check-in/check-out date, số khách, filters (giá, rating, amenities)P0
View hotel detailsXem thông tin chi tiết khách sạn: hình ảnh, mô tả, room types, giá, reviewsP0
View room availabilityXem số phòng còn trống cho từng loại phòng, từng ngàyP0
Reserve a roomĐặt phòng: chọn room type, ngày, nhập thông tin khách, thanh toánP0
Cancel reservationHủy đặt phòng, refund theo chính sáchP0
Admin: manage inventoryHotel admin cập nhật số phòng, giá, đóng/mở phòng theo mùaP0
Admin: view reservationsHotel admin xem danh sách booking, check-in/check-out statusP1
Price managementDynamic pricing theo mùa, ngày lễ, demandP1
Loyalty programTích điểm, ưu đãi cho khách hàng thân thiếtP2

1.2 Non-Functional Requirements

RequirementTargetLý do
Availability99.99% (4 nines)Downtime = mất doanh thu trực tiếp
ConsistencyStrong consistency cho reservationKhông được bán quá số phòng
Latency (search)P99 < 500msUser experience — search phải nhanh
Latency (reservation)P99 < 2s (bao gồm payment)Chấp nhận chậm hơn search vì có payment
DurabilityZero data loss cho reservationMất booking = mất tiền + mất khách
ScalabilityHandle peak 10x normal trafficDịp lễ, flash sale

1.3 Scale Estimation (Phạm vi bài toán)

Thông sốGiá trịGhi chú
Số khách sạn5,000Quy mô Agoda Vietnam
Tổng số phòng1,000,000 (1M)Trung bình 200 phòng/khách sạn
Số loại phòng trung bình20 / khách sạnStandard, Deluxe, Suite, Family, etc.
Read:Write ratio3:7 cho reservation pageNhiều người xem nhưng ít người đặt
Occupancy rate trung bình70%Industry average
Peak season multiplier10xTết, Noel, summer holidays
Average reservation duration2-3 đêmBusiness + leisure mix

Chú ý: Tỷ lệ read:write 3:7 cho reservation page là KHÁC với search page. Search page là read-heavy (90%+ read). Reservation page có nhiều write vì mỗi lần đặt phòng = write inventory + write reservation + write payment.


Step 2 — High-Level Design

2.1 System Components Overview

ComponentVai tròAnalogy
Hotel ServiceQuản lý thông tin khách sạn, phòng, hình ảnh, amenitiesCatalog của khách sạn
Search ServiceTìm kiếm khách sạn theo nhiều tiêu chí, rankingGoogle cho khách sạn
Rate ServiceQuản lý giá phòng theo ngày, mùa, demand (dynamic pricing)Bộ phận định giá
Inventory ServiceQuản lý số phòng khả dụng theo ngày — THE critical serviceKho hàng
Reservation ServiceXử lý đặt phòng, hủy phòng, thay đổi bookingQuầy lễ tân
Payment ServiceTích hợp PSP, xử lý thanh toán, refundThu ngân
Notification ServiceGửi email/SMS xác nhận booking, reminder check-inNhân viên CSKH

2.2 High-Level Architecture

flowchart TB
    subgraph "Client Layer"
        WEB[Web App<br/>React / Next.js]
        MOBILE[Mobile App<br/>iOS / Android]
        ADMIN[Hotel Admin<br/>Dashboard]
    end

    subgraph "API Gateway"
        GW[API Gateway<br/>Rate Limiting · Auth · Routing]
    end

    subgraph "Core Services"
        HOTEL[Hotel Service<br/>Hotel & Room Info]
        SEARCH[Search Service<br/>Elasticsearch]
        RATE[Rate Service<br/>Dynamic Pricing]
        INV[Inventory Service<br/>Room Availability]
        RESV[Reservation Service<br/>Booking Management]
        PAY[Payment Service<br/>PSP Integration]
    end

    subgraph "Supporting Services"
        NOTIFY[Notification Service<br/>Email · SMS · Push]
        LOYALTY[Loyalty Service<br/>Points · Rewards]
    end

    subgraph "Data Stores"
        PGDB[(PostgreSQL<br/>Reservation · Inventory)]
        REDIS[(Redis<br/>Cache · Session)]
        ES[(Elasticsearch<br/>Hotel Search Index)]
        MQ[Message Queue<br/>Kafka]
    end

    subgraph "External"
        PSP[PSP<br/>Stripe · VNPay]
        CDN[CDN<br/>Hotel Images]
    end

    WEB --> GW
    MOBILE --> GW
    ADMIN --> GW

    GW --> HOTEL
    GW --> SEARCH
    GW --> RESV

    HOTEL --> PGDB
    HOTEL --> CDN
    SEARCH --> ES
    RATE --> PGDB
    RATE --> REDIS
    INV --> PGDB
    INV --> REDIS
    RESV --> PGDB
    RESV --> INV
    RESV --> PAY
    RESV --> MQ
    PAY --> PSP

    MQ --> NOTIFY
    MQ --> LOYALTY

    style INV fill:#e53935,color:#fff
    style RESV fill:#1e88e5,color:#fff
    style PGDB fill:#43a047,color:#fff

Highlight: Inventory Service (màu đỏ) là critical path — mỗi reservation đều phải đi qua nó. Nếu Inventory Service chậm hoặc sai, toàn bộ hệ thống bị ảnh hưởng.

2.3 API Design Overview

APIMethodMô tả
/hotels/searchGETTìm khách sạn theo location, date, filters
/hotels/{id}GETXem chi tiết khách sạn
/hotels/{id}/roomsGETXem danh sách room types và giá
/hotels/{id}/rooms/availabilityGETCheck số phòng còn trống theo ngày
/reservationsPOSTTạo reservation mới
/reservations/{id}GETXem chi tiết reservation
/reservations/{id}/cancelPOSTHủy reservation
/admin/hotels/{id}/inventoryPUTCập nhật số phòng (admin)
/admin/hotels/{id}/ratesPUTCập nhật giá phòng (admin)

2.4 Basic Reservation Flow (High-Level)

1. User search khach san tai "Da Nang" cho ngay 31/12 - 02/01
2. Search Service tra ve danh sach khach san co phong trong
3. User chon "Muong Thanh Grand" → xem room types va gia
4. User chon "Deluxe Room" — hien thi "con 3 phong" — click "Dat ngay"
5. Reservation Service: kiem tra inventory → con phong → tao reservation (PENDING)
6. User nhap thong tin + thanh toan
7. Payment Service: charge card hoac authorization hold
8. Payment thanh cong → Reservation chuyen CONFIRMED → Inventory giam 1
9. Notification Service: gui email xac nhan cho user

Step 3 — Deep Dive

3.1 Data Model — Mô hình dữ liệu

Hotel Table

FieldTypeMô tả
hotel_idUUIDPrimary key
nameVARCHAR(255)Tên khách sạn
addressTEXTĐịa chỉ
cityVARCHAR(100)Thành phố
countryVARCHAR(100)Quốc gia
latitudeDECIMAL(10,8)Vĩ độ
longitudeDECIMAL(11,8)Kinh độ
star_ratingSMALLINTSố sao (1-5)
descriptionTEXTMô tả chi tiết
amenitiesJSONBTiện ích (pool, gym, spa, wifi…)
imagesJSONBDanh sách URL hình ảnh
statusENUMACTIVE, INACTIVE, SUSPENDED
created_atTIMESTAMPThời điểm tạo
updated_atTIMESTAMPThời điểm cập nhật

Room Type Table

FieldTypeMô tả
room_type_idUUIDPrimary key
hotel_idUUIDForeign key → Hotel
nameVARCHAR(100)Tên loại phòng (Standard, Deluxe, Suite)
descriptionTEXTMô tả phòng
max_occupancySMALLINTSố người tối đa
base_priceBIGINTGiá cơ bản (đơn vị nhỏ nhất — VND đồng)
amenitiesJSONBTiện ích phòng (minibar, balcony, bathtub)
imagesJSONBHình ảnh loại phòng
statusENUMACTIVE, INACTIVE

Room Type Inventory Table — THE CORE TABLE

FieldTypeMô tả
inventory_idUUIDPrimary key
hotel_idUUIDForeign key → Hotel
room_type_idUUIDForeign key → Room Type
dateDATENgày cụ thể
total_inventoryINTTổng số phòng loại này
total_reservedINTSố phòng đã đặt
total_availableINTSố phòng còn trống (= total - reserved)
versionINTOptimistic locking version

Tại sao có bảng inventory riêng theo ngày? Vì số phòng khả dụng khác nhau mỗi ngày. Ngày 31/12 có thể hết phòng nhưng ngày 02/01 còn nhiều. Hotel admin cũng có thể đóng một số phòng để bảo trì vào ngày cụ thể. Schema này cho phép quản lý inventory per-day granularity.

Unique constraint trên inventory table:

UNIQUE (hotel_id, room_type_id, date)

Mỗi hotel + room type + date chỉ có đúng một record inventory.

CHECK constraint chống overbooking:

CHECK (total_reserved <= total_inventory)
-- Hoac voi overbooking allowance:
CHECK (total_reserved <= FLOOR(total_inventory * (1 + overbooking_percentage / 100.0)))

Reservation Table

FieldTypeMô tả
reservation_idUUIDPrimary key, đồng thời là idempotency key
hotel_idUUIDForeign key → Hotel
room_type_idUUIDForeign key → Room Type
guest_idUUIDForeign key → User
check_in_dateDATENgày check-in
check_out_dateDATENgày check-out
num_roomsSMALLINTSố phòng đặt
total_priceBIGINTTổng giá (VND)
currencyVARCHAR(3)ISO 4217
statusENUMPENDING, CONFIRMED, CANCELLED, CHECKED_IN, CHECKED_OUT, NO_SHOW
payment_idUUIDForeign key → Payment
special_requestsTEXTYêu cầu đặc biệt
created_atTIMESTAMPThời điểm tạo
updated_atTIMESTAMPThời điểm cập nhật
cancelled_atTIMESTAMPThời điểm hủy (nullable)
cancellation_reasonTEXTLý do hủy (nullable)

Reservation State Machine

stateDiagram-v2
    [*] --> PENDING: User click "Dat phong"

    PENDING --> CONFIRMED: Payment thanh cong
    PENDING --> CANCELLED: Payment fail / timeout / user cancel

    CONFIRMED --> CHECKED_IN: Khach den nhan phong
    CONFIRMED --> CANCELLED: Huy truoc ngay check-in (theo policy)
    CONFIRMED --> NO_SHOW: Khach khong den

    CHECKED_IN --> CHECKED_OUT: Khach tra phong

    CANCELLED --> [*]
    NO_SHOW --> [*]
    CHECKED_OUT --> [*]

Allowed state transitions:

  • PENDING → CONFIRMED: VALID (payment success)
  • PENDING → CANCELLED: VALID (payment fail, timeout, user cancel)
  • CONFIRMED → CHECKED_IN: VALID (guest arrives)
  • CONFIRMED → CANCELLED: VALID (guest cancels before check-in date, subject to cancellation policy)
  • PENDING → CHECKED_IN: INVALID (không thể check-in khi chưa thanh toán)
  • CHECKED_OUT → CONFIRMED: INVALID (không thể quay lại trạng thái trước)

3.2 Reservation Flow — Luồng đặt phòng chi tiết

Two-Phase Reservation: Tentative Hold → Confirmed

Reservation không phải là một bước duy nhất. Nó là 2 phases:

PhaseMô tảThời gianInventory impact
Phase 1: Tentative HoldUser click “Đặt phòng” → hệ thống tạm giữ phòng (PENDING)10-15 phútInventory giảm (hold)
Phase 2: ConfirmPayment thành công → reservation chuyển CONFIRMEDSau paymentInventory confirmed

Tại sao cần 2 phases? Vì giữa lúc user click “Đặt phòng” và lúc payment hoàn tất có thể mất 5-10 phút (nhập thông tin thẻ, OTP, 3D Secure). Trong thời gian đó, phải “giữ” phòng để người khác không đặt mất. Nhưng nếu user không thanh toán trong 10-15 phút → release hold → trả phòng lại inventory.

Reservation Sequence Diagram

sequenceDiagram
    participant U as User
    participant FE as Frontend
    participant RS as Reservation Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant DB as PostgreSQL
    participant NS as Notification Service

    U->>FE: Click "Dat phong Deluxe"<br/>Check-in: 31/12, Check-out: 02/01
    FE->>RS: POST /reservations<br/>{hotel_id, room_type_id, dates, guest_info}

    Note over RS: Generate reservation_id<br/>(cung la idempotency key)

    RS->>IS: Check availability<br/>{hotel_id, room_type_id, dates}
    IS->>DB: SELECT total_available<br/>FROM room_type_inventory<br/>WHERE date IN (31/12, 01/01)

    alt Con phong (available >= 1)
        IS-->>RS: Available = YES

        RS->>IS: Reserve (tentative hold)
        IS->>DB: UPDATE room_type_inventory<br/>SET total_reserved += 1<br/>WHERE date IN (31/12, 01/01)<br/>AND total_available > 0

        Note over IS: Inventory giam 1 cho moi dem

        IS-->>RS: Hold confirmed

        RS->>DB: INSERT reservation<br/>(status = PENDING)
        RS-->>FE: Reservation PENDING<br/>Redirect to payment

        Note over FE: Timer: 10 phut de thanh toan

        U->>FE: Nhap thong tin thanh toan
        FE->>PS: Process payment<br/>{reservation_id, amount}
        PS-->>FE: Payment SUCCESS

        FE->>RS: Confirm reservation<br/>{reservation_id, payment_id}
        RS->>DB: UPDATE reservation<br/>SET status = CONFIRMED

        RS-->>FE: Booking CONFIRMED!
        RS->>NS: Send confirmation email
        NS-->>U: Email: "Dat phong thanh cong"

    else Het phong (available = 0)
        IS-->>RS: Available = NO
        RS-->>FE: Sorry, het phong!
    end

Hold Expiration — Xử lý khi user không thanh toán

sequenceDiagram
    participant CRON as Scheduler<br/>(chay moi phut)
    participant RS as Reservation Service
    participant IS as Inventory Service
    participant DB as PostgreSQL

    CRON->>RS: Check expired holds

    RS->>DB: SELECT reservations<br/>WHERE status = PENDING<br/>AND created_at < NOW() - 10 min

    Note over RS: Tim thay 5 reservations<br/>da qua 10 phut chua thanh toan

    loop Moi reservation het han
        RS->>DB: UPDATE reservation<br/>SET status = CANCELLED
        RS->>IS: Release hold
        IS->>DB: UPDATE room_type_inventory<br/>SET total_reserved -= 1<br/>WHERE date IN (reservation dates)
        Note over IS: Tra phong lai inventory
    end

Aha Moment: Hold expiration là critical. Nếu không có mechanism này, user có thể “giữ” phòng mãi mà không bao giờ thanh toán → hết phòng ảo (phòng trống nhưng không ai đặt được). Phải có background job chạy mỗi phút để release expired holds.

3.3 Concurrency Control — THE Core Problem

Đây là bài toán khó nhất của hotel reservation system. Khi 200 người cùng click “Đặt phòng” cho cùng 1 room type vào cùng 1 ngày, và chỉ còn 3 phòng, làm sao đảm bảo chỉ có 3 người đặt thành công?

Vấn đề: Race Condition

Thread A: Read available = 3 → Reserve 1 → Write available = 2
Thread B: Read available = 3 → Reserve 1 → Write available = 2 (SAI! Phai la 1)
Thread C: Read available = 3 → Reserve 1 → Write available = 2 (SAI! Phai la 0)

Ket qua: 3 nguoi dat nhung inventory chi giam 1 → Overbooking!

3 approaches chính để giải quyết:

Approach 1: Pessimistic Locking (SELECT FOR UPDATE)

Cơ chế: Lock row trước khi đọc. Các transaction khác phải chờ cho đến khi lock được release.

Flow:

Transaction A:
  BEGIN;
  SELECT total_reserved, total_available
  FROM room_type_inventory
  WHERE hotel_id = 'H001' AND room_type_id = 'RT001' AND date = '2026-12-31'
  FOR UPDATE;  -- Lock row!
  -- total_reserved = 47, total_available = 3

  UPDATE room_type_inventory
  SET total_reserved = 48, total_available = 2
  WHERE inventory_id = '...';
  COMMIT;  -- Release lock

Transaction B: (cho cho den khi A commit)
  BEGIN;
  SELECT ... FOR UPDATE;  -- Now available = 2
  UPDATE ... SET total_reserved = 49, total_available = 1;
  COMMIT;

Transaction C: (cho cho den khi B commit)
  BEGIN;
  SELECT ... FOR UPDATE;  -- Now available = 1
  UPDATE ... SET total_reserved = 50, total_available = 0;
  COMMIT;

Transaction D: (cho cho den khi C commit)
  BEGIN;
  SELECT ... FOR UPDATE;  -- Now available = 0
  -- Khong du phong → ROLLBACK, bao user "het phong"
  ROLLBACK;
Ưu điểmNhược điểm
Đơn giản, dễ hiểuGiảm throughput — các transaction phải xếp hàng chờ
Chắc chắn không overbookingCó thể deadlock nếu lock nhiều rows sai thứ tự
Phù hợp khi contention caoLatency tăng khi nhiều người cùng đặt

Approach 2: Optimistic Locking (Version Column)

Cơ chế: Không lock khi đọc. Khi write, kiểm tra version có thay đổi không. Nếu thay đổi → conflict → retry.

Flow:

Transaction A:
  -- Step 1: Read (KHONG lock)
  SELECT total_reserved, total_available, version
  FROM room_type_inventory
  WHERE hotel_id = 'H001' AND room_type_id = 'RT001' AND date = '2026-12-31';
  -- total_reserved = 47, total_available = 3, version = 10

  -- Step 2: Update voi version check
  UPDATE room_type_inventory
  SET total_reserved = 48, total_available = 2, version = 11
  WHERE inventory_id = '...' AND version = 10;
  -- affected_rows = 1 → SUCCESS

Transaction B: (doc cung luc voi A)
  -- Step 1: Read
  SELECT ... -- total_reserved = 47, total_available = 3, version = 10

  -- Step 2: Update voi version check
  UPDATE ... SET total_reserved = 48, version = 11
  WHERE ... AND version = 10;
  -- affected_rows = 0 → CONFLICT! (A da update version len 11)

  -- Step 3: RETRY tu Step 1
  SELECT ... -- total_reserved = 48, total_available = 2, version = 11
  UPDATE ... SET total_reserved = 49, version = 12
  WHERE ... AND version = 11;
  -- affected_rows = 1 → SUCCESS
Ưu điểmNhược điểm
Không blocking — read không bị lockNhiều retry khi contention cao (hot room, peak season)
Throughput cao hơn pessimisticRetry storm: 200 người cùng retry → chỉ 1 người thành công mỗi lần
Phù hợp khi conflict ítLatency không dự đoán (phụ thuộc số lần retry)

Approach 3: Database Constraints (CHECK Constraint)

Cơ chế: Dùng database constraint để enforce rule. Let DB handle concurrency.

Flow:

-- Tao constraint khi tao table:
ALTER TABLE room_type_inventory
ADD CONSTRAINT chk_no_overbooking
CHECK (total_reserved <= total_inventory);

-- Moi reservation chi can:
UPDATE room_type_inventory
SET total_reserved = total_reserved + 1
WHERE hotel_id = 'H001'
  AND room_type_id = 'RT001'
  AND date = '2026-12-31';

-- Neu total_reserved + 1 > total_inventory
-- → CHECK constraint violation → DB tu reject
-- → Application nhan error → bao user "het phong"
Ưu điểmNhược điểm
Đơn giản nhất — chỉ cần 1 UPDATE statementDựa hoàn toàn vào DB — khó customize logic
DB tự xử lý concurrency (row-level lock của UPDATE là atomic)Error handling: phải catch constraint violation error
Không cần version column, không cần explicit lockKhó implement overbooking percentage (cần phức tạp hơn)
Reliable — DB engine đã được test kỹ lưỡngPerformance phụ thuộc vào DB engine

So sánh 3 approaches

Tiêu chíPessimistic LockingOptimistic LockingDB Constraints
Độ phức tạpTrung bìnhCao (retry logic)Thấp
ThroughputThấp (blocking)Cao (no blocking)Cao
ConsistencyStrongEventual (retry)Strong
Contention caoOK nhưng chậmNhiều retry → chậmOK
Deadlock riskCó (multi-row lock)KhôngKhông
Overbooking preventionChắc chắnChắc chắn (sau retry)Chắc chắn
Khi nào dùngInventory update quan trọng, moderate trafficRead-heavy, low contentionKhi muốn đơn giản, trust DB
Hotel reservationPhù hợp cho peak seasonPhù hợp cho ngày thườngRecommended — đơn giản và hiệu quả

Recommendation từ Alex Xu: Dùng Database Constraints (Approach 3) làm primary strategy. Lý do: đơn giản nhất, DB engine đã tối ưu cho concurrent updates, và CHECK constraint đảm bảo không bao giờ overbooking (trừ khi cố tình). Kết hợp với Optimistic Locking (version column) cho các trường hợp cần retry logic.

3.4 Overbooking Strategy — Chiến lược bán quá

Overbooking là gì?

Overbooking là việc cố tình bán nhiều phòng hơn số phòng thực tế. Nghe như bug nhưng thực ra là business decision.

IndustryOverbooking rateLý do
Airlines5-15%~5% hành khách no-show. Bán 105 vé cho 100 ghế → tối ưu doanh thu
Hotels5-10%~10% khách cancel muộn hoặc no-show
Car rental10-20%Cao hơn vì khách thường thay đổi kế hoạch

Tại sao hotel cần overbooking?

Tình huốngKhông overbookingCó overbooking
100 phòng, 10% no-showBán 100 phòng, 10 khách no-show → 90% occupancyBán 110 phòng, 10 no-show → 100% occupancy
Doanh thuMất 10% doanh thuTối đa doanh thu
Rủi roKhông có rủi roNếu ít no-show hơn dự kiến → phải “relocate” khách

Implementation: Configurable Overbooking Threshold

Thêm cột overbooking_percentage vào room_type_inventory:

FieldTypeMô tả
overbooking_percentageDECIMAL(5,2)% overbooking cho phép. Default = 0 (không overbooking)

Logic kiểm tra availability:

Ví dụ: Hotel có 100 phòng Deluxe, overbooking 10%:

CHECK constraint với overbooking:

CHECK (total_reserved <= FLOOR(total_inventory * (1 + overbooking_percentage / 100.0)))

Aha Moment: Overbooking KHÔNG phải là bug — nó là business decision được cấu hình bởi hotel manager. Mỗi khách sạn có overbooking rate khác nhau. Luxury hotel (Ritz-Carlton) thường overbooking 0-2% vì cost của relocation rất cao. Budget hotel có thể overbooking 10-15%.

Xử lý khi overbooking xảy ra (khách đến nhưng hết phòng)

BướcHành động
1Hệ thống phát hiện: số khách check-in > số phòng thực tế
2Tìm khách sạn đối tác (partner hotel) có phòng trống
3Upgrade khách lên phòng tốt hơn (nếu còn)
4Relocate khách sang partner hotel + chịu chi phí
5Compensation: refund + voucher cho lần sau
6Ghi nhận metric: overbooking_relocation_count

3.5 Idempotency — Chống đặt phòng trùng lặp

Vấn đề: Client Retry → Double Booking

User click "Dat phong" → Network cham → Khong nhan response
→ User click "Dat phong" lan 2
→ Server nhan 2 request → Tao 2 reservation → Charge 2 lan!

Đây là vấn đề kinh điển trong mọi transaction system. Giải pháp: Idempotency Key.

Implementation: reservation_id là Idempotency Key

BướcMô tả
1Frontend generate reservation_id (UUID v4) trước khi gửi request
2Gửi request: POST /reservations với reservation_id trong body
3Server check: SELECT * FROM reservations WHERE reservation_id = ?
4aNếu không tồn tại → tạo reservation mới
4bNếu đã tồn tại → trả về reservation cũ (KHÔNG tạo mới)

Database enforcement:

UNIQUE INDEX ON reservations(reservation_id)

Nếu 2 request cùng reservation_id đến cùng lúc:

  • Request 1: INSERT thành công
  • Request 2: UNIQUE constraint violation → catch error → return existing reservation

Tại sao frontend generate ID? Vì nếu server generate ID, khi response bị mất (network timeout), client không biết ID → không thể retry an toàn. Khi frontend generate ID, client luôn gửi cùng ID khi retry → server nhận ra request trùng → trả về kết quả cũ.

Idempotency cho Payment

Tương tự, payment cũng cần idempotency:

FieldVai trò
reservation_idIdempotency key cho reservation
payment_idempotency_keyIdempotency key gửi cho PSP (Stripe Idempotency-Key header)
POST /payments
Headers: Idempotency-Key: {reservation_id}
Body: {amount: 2000000, currency: "VND"}

→ PSP (Stripe) se khong charge 2 lan voi cung Idempotency-Key

Aha Moment: Idempotency key ngăn double charge — một trong những lỗi nghiêm trọng nhất trong booking system. Khách hàng bị charge 2 lần = mất uy tín + refund process phức tạp + review xấu.

3.6 Handling High Concurrency During Flash Sales

Vấn đề: 1000 người cùng đặt 10 phòng

Trong flash sales (giá sốc, Black Friday), có thể có hàng ngàn request đồng thời cho 1 hotel với số phòng rất hạn chế. Lúc này:

  • Optimistic locking: retry storm — 1000 người retry, chỉ 1 thành công mỗi vòng → 999 retry → latency bùng
  • Pessimistic locking: 1000 transaction xếp hàng chờ → timeout → user experience tệ
  • DB constraint: 1000 UPDATE đồng thời → DB row-level lock → vẫn bottleneck

Giải pháp: Queue-Based Reservation

Thay vì để 1000 request trực tiếp vào DB, dùng message queue để serialize requests:

flowchart LR
    subgraph "Incoming Requests"
        R1[Request 1]
        R2[Request 2]
        R3[Request 3]
        R4[Request ...]
        R5[Request 1000]
    end

    subgraph "Queue"
        MQ[Message Queue<br/>Kafka / Redis Stream<br/>FIFO]
    end

    subgraph "Consumer"
        C1[Reservation Worker<br/>Process sequentially]
    end

    subgraph "Database"
        DB[(PostgreSQL<br/>Inventory)]
    end

    subgraph "Result"
        S[SUCCESS<br/>Phong da dat]
        F[FAILED<br/>Het phong]
    end

    R1 --> MQ
    R2 --> MQ
    R3 --> MQ
    R4 --> MQ
    R5 --> MQ
    MQ --> C1
    C1 --> DB
    C1 --> S
    C1 --> F

Flow:

BướcMô tả
11000 requests đến → tất cả được đưa vào message queue
2API trả về ngay: “Yêu cầu của bạn đang được xử lý” (202 Accepted)
3Consumer đọc từ queue, xử lý tuần tự (1 request một lúc)
4Consumer check inventory → còn phòng → reserve → trả kết quả qua WebSocket/polling
5Hết phòng → các request còn lại bị reject
6Notification gửi cho user: “Đặt phòng thành công” hoặc “Hết phòng”

Trade-off: Chuyển từ synchronous (click → kết quả ngay) sang asynchronous (click → chờ → kết quả sau). User experience thay đổi: hiển thị “Đang xử lý…” thay vì kết quả liền. Nhưng đảm bảo không overbookingkhông crash khi traffic spike.

Redis cho Fast Inventory Check

Trước khi đưa request vào queue, dùng Redis để pre-check inventory:

BướcMô tả
1Khi inventory thay đổi → update Redis: SET inv:H001:RT001:2026-12-31 3 (còn 3 phòng)
2Request đến → check Redis: GET inv:H001:RT001:2026-12-31
3Nếu Redis = 0 → trả về “Hết phòng” ngay, KHÔNG cần vào queue
4Nếu Redis > 0 → đưa vào queue để xử lý
5Sau khi reserve thành công → DECR inv:H001:RT001:2026-12-31

Lưu ý quan trọng: Redis chỉ là pre-check (approximate), KHÔNG phải source of truth. Source of truth luôn là PostgreSQL. Redis có thể sai (stale data) → nhưng DB constraint sẽ bắt lỗi cuối cùng.

Multi-layer protection: Redis reject 90% request nhanh (hết phòng) → Queue handle 10% còn lại tuần tự → DB constraint bảo vệ cuối cùng. Ba layer này kết hợp đảm bảo zero overbooking với high throughput.

3.7 Caching Strategy

Hotel & Room Data — Cache-Friendly

Dữ liệuCache strategyTTLLý do
Hotel info (tên, địa chỉ, amenities)Cache-aside với Redis1 giờThay đổi ít, read nhiều
Room type info (mô tả, hình ảnh)Cache-aside với Redis1 giờTương tự hotel info
Hotel imagesCDN cache24 giờStatic content, rất ít thay đổi
Hotel reviews/ratingsCache-aside15 phútThay đổi vừa phải
Search resultsCache-aside với Elasticsearch5 phútThay đổi theo inventory

Inventory Data — NGUY HIỂM khi cache

Vấn đềGiải thích
Stale inventoryCache nói “còn 2 phòng” nhưng thực tế đã hết → user đặt phòng → fail → bad UX
Cache invalidationMỗi khi có reservation/cancellation → phải invalidate cache ngay
Race conditionInvalidate cache → 100 requests đọc từ DB cùng lúc → cache stampede
Flash saleInventory thay đổi liên tục → cache luôn bị invalidate → cache hit rate ~ 0%

Kết luận: KHÔNG nên cache inventory data cho real-time availability check. Lý do:

  1. Inventory thay đổi quá thường xuyên (mỗi reservation → thay đổi)
  2. Sai lệch inventory = overbooking hoặc lost revenue
  3. Cache invalidation phức tạp và error-prone

Aha Moment: “Not everything should be cached”. Inventory là write-heavy, accuracy-critical data. Cache nó sẽ tạo nhiều vấn đề hơn giải quyết. Đọc trực tiếp từ DB (với proper indexing) là an toàn hơn.

Ngoại lệ: Redis cho flash sale (section 3.6) là approximate pre-check, không phải cache cho accurate availability. Nó chỉ để reject request nhanh khi chắc chắn hết phòng.

Dữ liệuCache?Lý do
Hotel infoRead-heavy, rarely changes
Room typeRead-heavy, rarely changes
Inventory (real-time)KHÔNGWrite-heavy, accuracy-critical
Price/RateCó (short TTL)Thay đổi hàng ngày nhưng không liên tục
Search resultsCó (short TTL)Chấp nhận slightly stale

3.8 Database Choice

ServiceDatabaseLý do
Reservation + InventoryPostgreSQLACID required, transaction support, CHECK constraints, strong consistency
Hotel SearchElasticsearchFull-text search, geo-spatial queries, faceted search (filter theo giá, rating, amenities)
CachingRedisIn-memory, sub-millisecond latency, data structures (String, Hash, Sorted Set)
SessionRedisFast session lookup, auto-expiry (TTL)
Hotel ImagesS3 + CDNObject storage cho hình ảnh, CDN cho fast delivery
AnalyticsClickHouse / BigQueryPhân tích booking trends, revenue, occupancy rates
Message QueueKafkaHigh throughput, durability, replay capability

Tại sao PostgreSQL cho reservation? Vì reservation cần ACID transactions — Atomicity (đặt phòng + giảm inventory phải cùng thành công hoặc cùng fail), Consistency (CHECK constraint), Isolation (concurrent updates), Durability (không mất data). NoSQL (MongoDB, DynamoDB) không có native ACID transaction phù hợp cho bài toán này.

3.9 Microservices Decomposition

flowchart TB
    subgraph "Public-Facing"
        SEARCH[Search Service<br/>Elasticsearch queries<br/>Stateless · Horizontally scalable]
        HOTEL[Hotel Service<br/>Hotel & Room CRUD<br/>Read-heavy · Cacheable]
    end

    subgraph "Core Transaction"
        RESV[Reservation Service<br/>Booking lifecycle<br/>Stateful · ACID required]
        INV[Inventory Service<br/>Room availability<br/>Hot path · Concurrency control]
        PAY[Payment Service<br/>PSP integration<br/>Idempotent · Retryable]
    end

    subgraph "Supporting"
        RATE[Rate Service<br/>Dynamic pricing<br/>Read-heavy · Cacheable]
        NOTIFY[Notification Service<br/>Email · SMS · Push<br/>Async · Fire-and-forget]
        LOYALTY[Loyalty Service<br/>Points · Rewards<br/>Eventually consistent]
    end

    SEARCH --> HOTEL
    SEARCH --> INV
    RESV --> INV
    RESV --> PAY
    RESV --> RATE
    RESV --> NOTIFY
    RESV --> LOYALTY

    style INV fill:#e53935,color:#fff
    style RESV fill:#1e88e5,color:#fff
    style PAY fill:#ff6f00,color:#fff

Scaling strategy cho từng service — tham chiếu Tuan-11-Microservices-Pattern:

ServiceScaling strategyLý do
Search ServiceHorizontal scale (nhiều instance)Stateless, read-heavy
Hotel ServiceHorizontal scale + cacheStateless, read-heavy, cacheable
Rate ServiceHorizontal scale + cacheRead-heavy, rate data cached
Inventory ServiceVertical scale (strong DB) + read replicasStateful, write-heavy, cần strong consistency
Reservation ServiceHorizontal scale (stateless logic) + shared DBBusiness logic stateless, DB là bottleneck
Payment ServiceHorizontal scale, idempotentStateless, PSP xử lý state
Notification ServiceHorizontal scale + queueAsync, tolerant of delay

Key insight: Inventory Service là bottleneck của hệ thống. Scale nó = scale DB (vertical + replication). Các service khác có thể horizontal scale dễ dàng vì stateless.

3.10 Data Consistency — Saga Pattern cho Cross-Service Transaction

Vấn đề: Distributed Transaction

Khi user đặt phòng, cần thực hiện 3 bước ở 3 service khác nhau:

  1. Inventory Service: Giảm số phòng
  2. Payment Service: Charge user
  3. Reservation Service: Confirm booking

Nếu bước 2 (Payment) fail sau khi bước 1 (Inventory) đã thành công → inconsistency: phòng đã bị giảm nhưng không có booking.

Saga Pattern — Choreography-Based

sequenceDiagram
    participant RS as Reservation Service
    participant IS as Inventory Service
    participant PS as Payment Service
    participant NS as Notification Service
    participant MQ as Kafka

    RS->>IS: 1. Reserve inventory (hold)
    IS-->>RS: Inventory held

    RS->>PS: 2. Process payment

    alt Payment SUCCESS
        PS-->>RS: Payment confirmed
        RS->>RS: 3. Update reservation → CONFIRMED
        RS->>MQ: Publish: BookingConfirmed
        MQ->>NS: 4. Send confirmation
        NS-->>RS: Email sent
    else Payment FAILED
        PS-->>RS: Payment failed
        RS->>IS: COMPENSATE: Release inventory
        IS-->>RS: Inventory released
        RS->>RS: Update reservation → CANCELLED
        RS->>MQ: Publish: BookingCancelled
        MQ->>NS: Send cancellation notice
    end

Saga Steps và Compensating Actions

StepServiceForward ActionCompensating Action (khi fail)
1Inventory ServiceReserve rooms (hold)Release hold → trả phòng lại
2Payment ServiceCharge card / Auth holdRefund / void authorization
3Reservation ServiceConfirm reservationCancel reservation
4Notification ServiceSend confirmation emailSend cancellation email

Failure scenarios:

ScenarioXảy ra gìCompensating actions
Step 1 fail (hết phòng)Báo user “hết phòng”Không cần compensate (chưa làm gì)
Step 2 fail (payment declined)Payment failCompensate Step 1: release inventory hold
Step 3 fail (DB error)Reservation không lưu đượcCompensate Step 2: refund payment. Compensate Step 1: release inventory
Step 4 fail (email fail)Email không gửi đượcKhông cần compensate (non-critical) — retry email later

Quan trọng: Thứ tự compensating actions là ngược lại với thứ tự forward actions. Compensate từ bước gần nhất ngược về bước đầu tiên. Tham khảo chi tiết tại Tuan-11-Microservices-Pattern.


Capacity Estimation — Ước lượng năng lực

Assumptions

Thông sốGiá trịGiải thích
Số khách sạn5,000Quy mô medium platform
Tổng số phòng1,000,000 (1M)200 phòng/khách sạn trung bình
Room types / khách sạn20Standard, Deluxe, Suite, etc.
Reservation/ngày100,000 (100K)~10% occupancy change per day
Search queries/ngày5,000,000 (5M)50x reservation volume
Average booking duration2.5 đêmBusiness + leisure mix
Peak-to-average ratio10xTết, Noel, summer
Average reservation payload1 KBJSON with guest info
Average search payload2 KBResponse with hotel list

Reservation QPS

Nhận xét: 11.6 TPS peak — đây là moderate scale. PostgreSQL có thể xử lý hàng ngàn TPS. Reservation không phải bottleneck về throughput. Concurrency control trên cùng 1 row mới là vấn đề chính.

Search QPS

Nhận xét: 579 QPS peak cho search. Elasticsearch có thể xử lý hàng ngàn QPS. Nhưng mỗi query có thể fan-out ra nhiều index → cần tối ưu query và cache.

Inventory Table Size

Số records trong room_type_inventory:

Kích thước mỗi record:

Nhận xét: 3.65 GB — vừa đủ cho single PostgreSQL instance. Nhưng cần partitioning theo date để tối ưu query (chỉ query các ngày trong tương lai, không cần scan ngày đã qua). Partition theo month là hợp lý.

Reservation Storage

Nhận xét: 182.5 GB cho 5 năm — PostgreSQL xử lý thoải mái. Có thể archive reservations cũ hơn 2 năm sang cold storage.

Cache Sizing

Nhận xét: 325 MB — Redis default max memory là 0 (unlimited), nhưng 325 MB là rất nhỏ. Một Redis instance 1 GB là đủ.

Tóm tắt Estimation

MetricValue
Reservation QPS (peak)~12/s
Search QPS (peak)~580/s
Inventory table size~3.65 GB
Reservation storage/year~36.5 GB
5-year reservation storage~182.5 GB
Redis cache~325 MB
Inventory records~36.5M

Security — Bảo mật

Payment Data Protection (PCI-DSS)

Tham khảo chi tiết tại Case-Design-Payment-SystemTuan-15-Data-Security-Encryption.

Nguyên tắcÁp dụng cho Hotel Reservation
KHÔNG lưu card numberDùng PSP (Stripe, VNPay) hosted payment page. Chỉ lưu token
TokenizationCard data → token. Bạn chỉ lưu token + last 4 digits
TLS 1.2+Mọi API call phải qua HTTPS. Không bao giờ truyền card data qua HTTP
PCI-DSS scope reductionDùng hosted payment page → giảm PCI-DSS compliance scope

Prevent Price Manipulation

Tấn côngMô tảPhòng chống
Price tamperingUser sửa giá trên frontend (inspect element, intercept request)Server-side price validation: luôn tính giá từ DB, KHÔNG tin giá từ client
Race condition exploitĐặt phòng với giá cũ sau khi giá đã tăngLock giá tại thời điểm tạo reservation, giá được tính trên server
Coupon abuseDùng coupon nhiều lần hoặc coupon của người khácUnique coupon per user, server-side validation, rate limiting

Price validation flow:

BướcMô tả
1Client gửi: {hotel_id, room_type_id, dates} — KHÔNG gửi price
2Server tính giá từ Rate Service: base_price x nights x tax
3Server trả về giá chính thức cho client hiển thị
4Client confirm → Server tính giá LẠI lần nữa trước khi charge
5Nếu giá thay đổi giữa lúc hiển thị và lúc confirm → thông báo user giá mới

Rule: KHÔNG BAO GIỜ tin giá từ client. Luôn tính giá trên server tại mỗi bước.

Rate Limiting — Chống Inventory Scraping

Tấn côngMô tảPhòng chống
Inventory scrapingBot crawl tất cả availability để bán lại (OTA arbitrage)Rate limiting: 100 requests/phút/IP
Search abuseBot search liên tục để theo dõi giáCAPTCHA sau 50 searches, rate limit per user
Reservation botBot tự động đặt phòng khi thấy giá thấpDevice fingerprinting, behavioral analysis
DDoSTấn công làm sập hệ thốngWAF (Web Application Firewall), CDN protection (Cloudflare)

Rate limiting strategy — tham khảo Tuan-09-Rate-Limiter:

EndpointRate limitLý do
/hotels/search100 req/min/IPChống scraping
/hotels/{id}/rooms/availability60 req/min/IPChống inventory scraping
/reservations (POST)10 req/min/userChống booking bot
/reservations/{id}/cancel5 req/min/userChống abuse

User Data Privacy (GDPR / PDPA)

Dữ liệuPhân loạiBảo vệ
Tên, email, SDTPII (Personally Identifiable Information)Encrypt at rest (AES-256), access control
Passport/CCCDSensitive PIIEncrypt + restricted access, auto-delete sau 30 ngày
Payment tokenPayment dataPCI-DSS compliant storage
Booking historyPersonal dataAccessible only by user + authorized staff
IP address, device infoTechnical dataLog rotation, anonymize after 90 ngày
GDPR RightImplementation
Right to accessAPI cho user xem toàn bộ data của họ
Right to deletionSoft delete + anonymize PII sau retention period
Right to portabilityExport booking history dạng JSON/CSV
Consent managementExplicit opt-in cho marketing emails

DevOps & Monitoring — Vận hành và giám sát

Key Metrics to Monitor

MetricMô tảAlert ThresholdSeverity
Reservation Success Rate% đặt phòng thành công (không tính hết phòng)< 95% → alertP1 (Critical)
Overbooking Rate% khách bị relocate do overbooking> 2% → alertP1
Inventory AccuracyInventory trong DB vs thực tế (sau reconciliation)Discrepancy > 0.1% → alertP1
Search Latency P99Thời gian search 99th percentile> 500ms → alertP2
Reservation Latency P99Thời gian đặt phòng E2E> 3s → alertP2
Payment Failure Rate% thanh toán thất bại> 10% → alertP1
Hold Expiration Rate% reservation PENDING bị timeout> 30% → investigateP3
Cache Hit Rate% request được serve từ cache< 80% → investigateP3
DB Connection PoolSố connection hiện tại / max> 80% → alertP2
Queue DepthSố message trong Kafka waiting> 10,000 → alertP2

Dashboard Layout

PanelMetricsVisualization
Booking HealthSuccess rate, volume, error breakdownTime series (last 24h)
InventoryOccupancy rate per city, overbooking eventsHeatmap + counter
Search PerformanceQPS, latency P50/P95/P99Line chart + histogram
PaymentSuccess/failure rate, PSP latencyPie chart + time series
InfrastructureCPU, memory, DB connections, cache hit rateGauge + sparkline
BusinessRevenue, ADR (Average Daily Rate), RevPARCounter + trend

Alerting Rules

AlertConditionAction
Overbooking detectedKhách check-in > số phòng availablePage on-call, tìm phòng thay thế
Payment gateway downPSP error rate > 50% cho 5 phútFailover sang PSP backup, page team
Inventory discrepancyDB inventory khác actual > thresholdFreeze booking cho hotel đó, investigate
Search degradationP99 > 2s cho 10 phútScale Elasticsearch nodes, check query performance
Expired hold spikeHold expiration rate > 50% trong 1 giờCheck payment flow, PSP latency

SLA Monitoring

SLATargetMeasurement
Uptime99.99% (52.6 phút downtime/năm)Synthetic monitoring mỗi 30 giây
Search availability99.95%Health check endpoint
Booking availability99.99%End-to-end booking test mỗi 5 phút
Data durability99.999999999% (11 nines)DB replication + backup verification

Mermaid Diagrams — Tổng hợp

Diagram 1: High-Level Architecture (đã có ở Section 2.2)

Diagram 2: Reservation Flow (đã có ở Section 3.2)

Diagram 3: Concurrency Control Comparison

flowchart TB
    subgraph "Approach 1: Pessimistic Locking"
        P1[Transaction A<br/>SELECT ... FOR UPDATE]
        P2[Row LOCKED]
        P3[Transaction B<br/>WAITING...]
        P4[A commits → B proceeds]

        P1 --> P2
        P2 --> P3
        P3 -.->|wait| P4
    end

    subgraph "Approach 2: Optimistic Locking"
        O1[Transaction A<br/>Read version=10]
        O2[Transaction B<br/>Read version=10]
        O3[A: UPDATE WHERE version=10<br/>SUCCESS → version=11]
        O4[B: UPDATE WHERE version=10<br/>CONFLICT → RETRY]

        O1 --> O3
        O2 --> O4
    end

    subgraph "Approach 3: DB Constraint"
        D1[Transaction A<br/>UPDATE reserved += 1]
        D2[Transaction B<br/>UPDATE reserved += 1]
        D3[DB: CHECK reserved <= total<br/>A: OK · B: OK or REJECT]

        D1 --> D3
        D2 --> D3
    end

    style P2 fill:#e53935,color:#fff
    style O4 fill:#ff6f00,color:#fff
    style D3 fill:#43a047,color:#fff

Diagram 4: Saga Pattern cho Reservation

flowchart TB
    START([User click Dat Phong]) --> S1

    subgraph "Forward Flow"
        S1[Step 1: Reserve Inventory<br/>Inventory Service]
        S2[Step 2: Process Payment<br/>Payment Service]
        S3[Step 3: Confirm Booking<br/>Reservation Service]
        S4[Step 4: Send Notification<br/>Notification Service]
    end

    subgraph "Compensating Flow"
        C1[Compensate: Release Inventory<br/>Inventory Service]
        C2[Compensate: Refund Payment<br/>Payment Service]
        C3[Compensate: Cancel Booking<br/>Reservation Service]
    end

    S1 -->|Success| S2
    S2 -->|Success| S3
    S3 -->|Success| S4
    S4 --> DONE([Booking Confirmed!])

    S1 -->|Fail| FAIL1([Het phong])
    S2 -->|Fail| C1
    S3 -->|Fail| C2
    C2 --> C1
    C1 --> FAIL2([Booking Failed<br/>User notified])

    style S1 fill:#1e88e5,color:#fff
    style S2 fill:#ff6f00,color:#fff
    style S3 fill:#43a047,color:#fff
    style S4 fill:#7b1fa2,color:#fff
    style C1 fill:#e53935,color:#fff
    style C2 fill:#e53935,color:#fff
    style C3 fill:#e53935,color:#fff

Diagram 5: Data Flow Overview

flowchart LR
    subgraph "Read Path"
        U1[User] -->|Search| GW1[API Gateway]
        GW1 -->|Query| ES[(Elasticsearch)]
        GW1 -->|Hotel details| CACHE[(Redis Cache)]
        CACHE -.->|Cache miss| DB1[(PostgreSQL)]
    end

    subgraph "Write Path"
        U2[User] -->|Reserve| GW2[API Gateway]
        GW2 -->|Create booking| RESV[Reservation Service]
        RESV -->|Check + Reserve| INV[Inventory Service]
        INV -->|UPDATE| DB2[(PostgreSQL)]
        RESV -->|Charge| PAY[Payment Service]
        PAY -->|API call| PSP[Stripe / VNPay]
    end

    subgraph "Async Path"
        DB2 -.->|CDC / Event| KAFKA[Kafka]
        KAFKA -.->|Update index| ES
        KAFKA -.->|Notification| NOTIFY[Email/SMS]
        KAFKA -.->|Analytics| DW[(Data Warehouse)]
    end

    style DB2 fill:#43a047,color:#fff
    style ES fill:#1e88e5,color:#fff
    style KAFKA fill:#ff6f00,color:#fff

Aha Moments & Pitfalls — Những điều rút ra

Aha Moments

#InsightGiải thích
1DB constraint thường là đủKhông cần implement lock phức tạp. CHECK (reserved <= total) + atomic UPDATE là đủ cho hầu hết trường hợp. DB engine đã được tối ưu cho việc này
2Overbooking là business decision, không phải bugAirlines, hotels đều làm việc này. Cấu hình overbooking percentage là feature, không phải defect
3Inventory caching là nguy hiểmCache stale inventory → overbooking hoặc lost revenue. Inventory nên đọc trực tiếp từ DB với proper indexing
4Idempotency ngăn double chargeKhông có idempotency → client retry → 2 bookings + 2 charges. reservation_id là idempotency key tự nhiên
5Two-phase reservation là bắt buộcPhải hold phòng trước khi payment. Không hold → người khác đặt mất trong lúc đang thanh toán
6Hold expiration là criticalKhông có hold expiration → “ghost bookings” chiếm inventory mãi mãi. Background job release expired holds
7Queue là giải pháp cho flash saleSerialize requests qua queue → xử lý tuần tự → không concurrency issue. Trade-off: async UX
8Saga pattern cho distributed transactionReserve → Pay → Confirm. Mỗi bước có compensating action. Không dùng 2PC vì blocking và fragile

Common Pitfalls

#PitfallHậu quảCách tránh
1Cache inventory và hiển thị như source of truthUser thấy “còn phòng” nhưng đặt thì hết → bad UX, mất uy tínĐọc inventory từ DB cho availability check, chỉ cache hotel info
2Không có hold expirationUser tạo reservation nhưng không thanh toán → phòng bị “giữ” mãiBackground job release PENDING reservations sau 10-15 phút
3Tin giá từ clientAttacker sửa giá 5,000,000 VND → 500 VNDLuôn tính giá trên server, KHÔNG tin request body
4Không có idempotency keyDouble booking, double charge khi client retryFrontend generate reservation_id trước khi gửi request
5Lock quá nhiều rows cùng lúcDeadlock khi 2 transactions lock các rows theo thứ tự khác nhauLock theo thứ tự cố định (sort by date), hoặc dùng optimistic locking
6Single point of failure ở Inventory ServiceInventory Service die → toàn bộ booking dieMulti-instance + DB replication + circuit breaker
7Không có Saga compensating actionPayment fail nhưng inventory vẫn bị holdMỗi forward action phải có compensating action tương ứng
8Không partition inventory table36.5M records → query chậmPartition theo date (monthly), chỉ query tương lai

Interview Tips

Câu hỏi interviewer có thể hỏiCách trả lời
”Làm sao xử lý 1000 người đặt cùng 1 phòng?”Trình bày 3 concurrency approaches, recommend DB constraint + queue cho flash sale
”Nếu payment fail giữa chừng, làm gì?”Saga pattern với compensating actions. Release inventory hold, notify user
”Overbooking thì sao?”Là business decision, configurable. Giải thích effective_capacity formula
”Cache inventory được không?”Giải thích tại sao nguy hiểm, chỉ cache hotel/room info, không cache real-time inventory
”Làm sao scale?”Stateless services horizontal scale. Inventory/DB là bottleneck → vertical scale + partitioning + read replicas

LinkLiên quan
Tuan-07-Database-Sharding-ReplicationInventory table partitioning, DB replication cho read scalability
Tuan-11-Microservices-PatternSaga pattern, service decomposition, inter-service communication
Case-Design-Payment-SystemPayment flow chi tiết, PCI-DSS, idempotency pattern
Tuan-08-Message-QueueKafka cho async processing, queue-based reservation
Tuan-06-Cache-StrategyCache-aside pattern cho hotel data, tại sao không cache inventory
Tuan-09-Rate-LimiterRate limiting chống scraping và booking bot
Tuan-14-AuthN-AuthZ-SecurityAuthentication, authorization cho admin và user
Tuan-15-Data-Security-EncryptionEncryption at rest/transit, PII protection
Tuan-02-Back-of-the-envelopeCapacity estimation methodology
Tuan-13-Monitoring-ObservabilityMonitoring, alerting, SLA tracking

Final takeaway: Hotel Reservation System là bài toán concurrency control trên shared inventory. Core problem không phải scale (QPS thấp), mà là accuracy — không được bán quá số phòng, không được charge 2 lần, không được mất booking. Ba kỹ thuật quan trọng nhất: DB constraints (chống overbooking), Idempotency (chống double booking), và Saga pattern (chống inconsistency). Master 3 điều này, bạn có thể giải quyết bất kỳ bài toán inventory nào — từ hotel đến e-commerce đến ticketing.