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:
- 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
- Đả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)
- Xử lý đồng thời — 200 người click “Đặt ngay” cùng lúc, chỉ 50 người thành công
- Không charge 2 lần — người dùng click retry vì mạng chậm, không được tạo 2 booking
- 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ức | Giải thích |
|---|---|
| Concurrency control | Hà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 accuracy | Số 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 prevention | Người dùng click đặt 2 lần, mạng chậm retry — không được tạo 2 reservation |
| Peak traffic | Dị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 performance | Tìm khách sạn theo location, giá, rating, amenities — dataset lớn, query phức tạp |
| Overbooking as business decision | Airlines 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ý do | Giải thích |
|---|---|
| Concurrency là core skill | Hiể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 pattern | E-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-critical | Search (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
| Requirement | Mô tả | Priority |
|---|---|---|
| Search hotels | Tìm khách sạn theo location, check-in/check-out date, số khách, filters (giá, rating, amenities) | P0 |
| View hotel details | Xem thông tin chi tiết khách sạn: hình ảnh, mô tả, room types, giá, reviews | P0 |
| View room availability | Xem số phòng còn trống cho từng loại phòng, từng ngày | P0 |
| Reserve a room | Đặt phòng: chọn room type, ngày, nhập thông tin khách, thanh toán | P0 |
| Cancel reservation | Hủy đặt phòng, refund theo chính sách | P0 |
| Admin: manage inventory | Hotel admin cập nhật số phòng, giá, đóng/mở phòng theo mùa | P0 |
| Admin: view reservations | Hotel admin xem danh sách booking, check-in/check-out status | P1 |
| Price management | Dynamic pricing theo mùa, ngày lễ, demand | P1 |
| Loyalty program | Tích điểm, ưu đãi cho khách hàng thân thiết | P2 |
1.2 Non-Functional Requirements
| Requirement | Target | Lý do |
|---|---|---|
| Availability | 99.99% (4 nines) | Downtime = mất doanh thu trực tiếp |
| Consistency | Strong consistency cho reservation | Không được bán quá số phòng |
| Latency (search) | P99 < 500ms | User 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 |
| Durability | Zero data loss cho reservation | Mất booking = mất tiền + mất khách |
| Scalability | Handle peak 10x normal traffic | Dị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ạn | 5,000 | Quy mô Agoda Vietnam |
| Tổng số phòng | 1,000,000 (1M) | Trung bình 200 phòng/khách sạn |
| Số loại phòng trung bình | 20 / khách sạn | Standard, Deluxe, Suite, Family, etc. |
| Read:Write ratio | 3:7 cho reservation page | Nhiều người xem nhưng ít người đặt |
| Occupancy rate trung bình | 70% | Industry average |
| Peak season multiplier | 10x | Tết, Noel, summer holidays |
| Average reservation duration | 2-3 đêm | Business + 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
| Component | Vai trò | Analogy |
|---|---|---|
| Hotel Service | Quản lý thông tin khách sạn, phòng, hình ảnh, amenities | Catalog của khách sạn |
| Search Service | Tìm kiếm khách sạn theo nhiều tiêu chí, ranking | Google cho khách sạn |
| Rate Service | Quản lý giá phòng theo ngày, mùa, demand (dynamic pricing) | Bộ phận định giá |
| Inventory Service | Quản lý số phòng khả dụng theo ngày — THE critical service | Kho hàng |
| Reservation Service | Xử lý đặt phòng, hủy phòng, thay đổi booking | Quầy lễ tân |
| Payment Service | Tích hợp PSP, xử lý thanh toán, refund | Thu ngân |
| Notification Service | Gửi email/SMS xác nhận booking, reminder check-in | Nhâ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
| API | Method | Mô tả |
|---|---|---|
/hotels/search | GET | Tìm khách sạn theo location, date, filters |
/hotels/{id} | GET | Xem chi tiết khách sạn |
/hotels/{id}/rooms | GET | Xem danh sách room types và giá |
/hotels/{id}/rooms/availability | GET | Check số phòng còn trống theo ngày |
/reservations | POST | Tạo reservation mới |
/reservations/{id} | GET | Xem chi tiết reservation |
/reservations/{id}/cancel | POST | Hủy reservation |
/admin/hotels/{id}/inventory | PUT | Cập nhật số phòng (admin) |
/admin/hotels/{id}/rates | PUT | Cậ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
| Field | Type | Mô tả |
|---|---|---|
hotel_id | UUID | Primary key |
name | VARCHAR(255) | Tên khách sạn |
address | TEXT | Địa chỉ |
city | VARCHAR(100) | Thành phố |
country | VARCHAR(100) | Quốc gia |
latitude | DECIMAL(10,8) | Vĩ độ |
longitude | DECIMAL(11,8) | Kinh độ |
star_rating | SMALLINT | Số sao (1-5) |
description | TEXT | Mô tả chi tiết |
amenities | JSONB | Tiện ích (pool, gym, spa, wifi…) |
images | JSONB | Danh sách URL hình ảnh |
status | ENUM | ACTIVE, INACTIVE, SUSPENDED |
created_at | TIMESTAMP | Thời điểm tạo |
updated_at | TIMESTAMP | Thời điểm cập nhật |
Room Type Table
| Field | Type | Mô tả |
|---|---|---|
room_type_id | UUID | Primary key |
hotel_id | UUID | Foreign key → Hotel |
name | VARCHAR(100) | Tên loại phòng (Standard, Deluxe, Suite) |
description | TEXT | Mô tả phòng |
max_occupancy | SMALLINT | Số người tối đa |
base_price | BIGINT | Giá cơ bản (đơn vị nhỏ nhất — VND đồng) |
amenities | JSONB | Tiện ích phòng (minibar, balcony, bathtub) |
images | JSONB | Hình ảnh loại phòng |
status | ENUM | ACTIVE, INACTIVE |
Room Type Inventory Table — THE CORE TABLE
| Field | Type | Mô tả |
|---|---|---|
inventory_id | UUID | Primary key |
hotel_id | UUID | Foreign key → Hotel |
room_type_id | UUID | Foreign key → Room Type |
date | DATE | Ngày cụ thể |
total_inventory | INT | Tổng số phòng loại này |
total_reserved | INT | Số phòng đã đặt |
total_available | INT | Số phòng còn trống (= total - reserved) |
version | INT | Optimistic 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
| Field | Type | Mô tả |
|---|---|---|
reservation_id | UUID | Primary key, đồng thời là idempotency key |
hotel_id | UUID | Foreign key → Hotel |
room_type_id | UUID | Foreign key → Room Type |
guest_id | UUID | Foreign key → User |
check_in_date | DATE | Ngày check-in |
check_out_date | DATE | Ngày check-out |
num_rooms | SMALLINT | Số phòng đặt |
total_price | BIGINT | Tổng giá (VND) |
currency | VARCHAR(3) | ISO 4217 |
status | ENUM | PENDING, CONFIRMED, CANCELLED, CHECKED_IN, CHECKED_OUT, NO_SHOW |
payment_id | UUID | Foreign key → Payment |
special_requests | TEXT | Yêu cầu đặc biệt |
created_at | TIMESTAMP | Thời điểm tạo |
updated_at | TIMESTAMP | Thời điểm cập nhật |
cancelled_at | TIMESTAMP | Thời điểm hủy (nullable) |
cancellation_reason | TEXT | Lý 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:
| Phase | Mô tả | Thời gian | Inventory impact |
|---|---|---|---|
| Phase 1: Tentative Hold | User click “Đặt phòng” → hệ thống tạm giữ phòng (PENDING) | 10-15 phút | Inventory giảm (hold) |
| Phase 2: Confirm | Payment thành công → reservation chuyển CONFIRMED | Sau payment | Inventory 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!
Có 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ểm | Nhược điểm |
|---|---|
| Đơn giản, dễ hiểu | Giảm throughput — các transaction phải xếp hàng chờ |
| Chắc chắn không overbooking | Có thể deadlock nếu lock nhiều rows sai thứ tự |
| Phù hợp khi contention cao | Latency 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ểm | Nhược điểm |
|---|---|
| Không blocking — read không bị lock | Nhiều retry khi contention cao (hot room, peak season) |
| Throughput cao hơn pessimistic | Retry storm: 200 người cùng retry → chỉ 1 người thành công mỗi lần |
| Phù hợp khi conflict ít | Latency 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ểm | Nhược điểm |
|---|---|
| Đơn giản nhất — chỉ cần 1 UPDATE statement | Dự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 lock | Khó implement overbooking percentage (cần phức tạp hơn) |
| Reliable — DB engine đã được test kỹ lưỡng | Performance phụ thuộc vào DB engine |
So sánh 3 approaches
| Tiêu chí | Pessimistic Locking | Optimistic Locking | DB Constraints |
|---|---|---|---|
| Độ phức tạp | Trung bình | Cao (retry logic) | Thấp |
| Throughput | Thấp (blocking) | Cao (no blocking) | Cao |
| Consistency | Strong | Eventual (retry) | Strong |
| Contention cao | OK nhưng chậm | Nhiều retry → chậm | OK |
| Deadlock risk | Có (multi-row lock) | Không | Không |
| Overbooking prevention | Chắc chắn | Chắc chắn (sau retry) | Chắc chắn |
| Khi nào dùng | Inventory update quan trọng, moderate traffic | Read-heavy, low contention | Khi muốn đơn giản, trust DB |
| Hotel reservation | Phù hợp cho peak season | Phù hợp cho ngày thường | Recommended — đơ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.
| Industry | Overbooking rate | Lý do |
|---|---|---|
| Airlines | 5-15% | ~5% hành khách no-show. Bán 105 vé cho 100 ghế → tối ưu doanh thu |
| Hotels | 5-10% | ~10% khách cancel muộn hoặc no-show |
| Car rental | 10-20% | Cao hơn vì khách thường thay đổi kế hoạch |
Tại sao hotel cần overbooking?
| Tình huống | Không overbooking | Có overbooking |
|---|---|---|
| 100 phòng, 10% no-show | Bán 100 phòng, 10 khách no-show → 90% occupancy | Bán 110 phòng, 10 no-show → 100% occupancy |
| Doanh thu | Mất 10% doanh thu | Tối đa doanh thu |
| Rủi ro | Không có rủi ro | Nế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:
| Field | Type | Mô tả |
|---|---|---|
overbooking_percentage | DECIMAL(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ước | Hành động |
|---|---|
| 1 | Hệ thống phát hiện: số khách check-in > số phòng thực tế |
| 2 | Tìm khách sạn đối tác (partner hotel) có phòng trống |
| 3 | Upgrade khách lên phòng tốt hơn (nếu còn) |
| 4 | Relocate khách sang partner hotel + chịu chi phí |
| 5 | Compensation: refund + voucher cho lần sau |
| 6 | Ghi 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ước | Mô tả |
|---|---|
| 1 | Frontend generate reservation_id (UUID v4) trước khi gửi request |
| 2 | Gửi request: POST /reservations với reservation_id trong body |
| 3 | Server check: SELECT * FROM reservations WHERE reservation_id = ? |
| 4a | Nếu không tồn tại → tạo reservation mới |
| 4b | Nế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:
| Field | Vai trò |
|---|---|
reservation_id | Idempotency key cho reservation |
payment_idempotency_key | Idempotency 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ước | Mô tả |
|---|---|
| 1 | 1000 requests đến → tất cả được đưa vào message queue |
| 2 | API trả về ngay: “Yêu cầu của bạn đang được xử lý” (202 Accepted) |
| 3 | Consumer đọc từ queue, xử lý tuần tự (1 request một lúc) |
| 4 | Consumer check inventory → còn phòng → reserve → trả kết quả qua WebSocket/polling |
| 5 | Hết phòng → các request còn lại bị reject |
| 6 | Notification 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 overbooking và khô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ước | Mô tả |
|---|---|
| 1 | Khi inventory thay đổi → update Redis: SET inv:H001:RT001:2026-12-31 3 (còn 3 phòng) |
| 2 | Request đến → check Redis: GET inv:H001:RT001:2026-12-31 |
| 3 | Nếu Redis = 0 → trả về “Hết phòng” ngay, KHÔNG cần vào queue |
| 4 | Nếu Redis > 0 → đưa vào queue để xử lý |
| 5 | Sau 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ệu | Cache strategy | TTL | Lý do |
|---|---|---|---|
| Hotel info (tên, địa chỉ, amenities) | Cache-aside với Redis | 1 giờ | Thay đổi ít, read nhiều |
| Room type info (mô tả, hình ảnh) | Cache-aside với Redis | 1 giờ | Tương tự hotel info |
| Hotel images | CDN cache | 24 giờ | Static content, rất ít thay đổi |
| Hotel reviews/ratings | Cache-aside | 15 phút | Thay đổi vừa phải |
| Search results | Cache-aside với Elasticsearch | 5 phút | Thay đổi theo inventory |
Inventory Data — NGUY HIỂM khi cache
| Vấn đề | Giải thích |
|---|---|
| Stale inventory | Cache nói “còn 2 phòng” nhưng thực tế đã hết → user đặt phòng → fail → bad UX |
| Cache invalidation | Mỗi khi có reservation/cancellation → phải invalidate cache ngay |
| Race condition | Invalidate cache → 100 requests đọc từ DB cùng lúc → cache stampede |
| Flash sale | Inventory 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:
- Inventory thay đổi quá thường xuyên (mỗi reservation → thay đổi)
- Sai lệch inventory = overbooking hoặc lost revenue
- 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ệu | Cache? | Lý do |
|---|---|---|
| Hotel info | Có | Read-heavy, rarely changes |
| Room type | Có | Read-heavy, rarely changes |
| Inventory (real-time) | KHÔNG | Write-heavy, accuracy-critical |
| Price/Rate | Có (short TTL) | Thay đổi hàng ngày nhưng không liên tục |
| Search results | Có (short TTL) | Chấp nhận slightly stale |
3.8 Database Choice
| Service | Database | Lý do |
|---|---|---|
| Reservation + Inventory | PostgreSQL | ACID required, transaction support, CHECK constraints, strong consistency |
| Hotel Search | Elasticsearch | Full-text search, geo-spatial queries, faceted search (filter theo giá, rating, amenities) |
| Caching | Redis | In-memory, sub-millisecond latency, data structures (String, Hash, Sorted Set) |
| Session | Redis | Fast session lookup, auto-expiry (TTL) |
| Hotel Images | S3 + CDN | Object storage cho hình ảnh, CDN cho fast delivery |
| Analytics | ClickHouse / BigQuery | Phân tích booking trends, revenue, occupancy rates |
| Message Queue | Kafka | High 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:
| Service | Scaling strategy | Lý do |
|---|---|---|
| Search Service | Horizontal scale (nhiều instance) | Stateless, read-heavy |
| Hotel Service | Horizontal scale + cache | Stateless, read-heavy, cacheable |
| Rate Service | Horizontal scale + cache | Read-heavy, rate data cached |
| Inventory Service | Vertical scale (strong DB) + read replicas | Stateful, write-heavy, cần strong consistency |
| Reservation Service | Horizontal scale (stateless logic) + shared DB | Business logic stateless, DB là bottleneck |
| Payment Service | Horizontal scale, idempotent | Stateless, PSP xử lý state |
| Notification Service | Horizontal scale + queue | Async, 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:
- Inventory Service: Giảm số phòng
- Payment Service: Charge user
- 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
| Step | Service | Forward Action | Compensating Action (khi fail) |
|---|---|---|---|
| 1 | Inventory Service | Reserve rooms (hold) | Release hold → trả phòng lại |
| 2 | Payment Service | Charge card / Auth hold | Refund / void authorization |
| 3 | Reservation Service | Confirm reservation | Cancel reservation |
| 4 | Notification Service | Send confirmation email | Send cancellation email |
Failure scenarios:
| Scenario | Xả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 fail | Compensate Step 1: release inventory hold |
| Step 3 fail (DB error) | Reservation không lưu được | Compensate Step 2: refund payment. Compensate Step 1: release inventory |
| Step 4 fail (email fail) | Email không gửi được | Khô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ạn | 5,000 | Quy mô medium platform |
| Tổng số phòng | 1,000,000 (1M) | 200 phòng/khách sạn trung bình |
| Room types / khách sạn | 20 | Standard, Deluxe, Suite, etc. |
| Reservation/ngày | 100,000 (100K) | ~10% occupancy change per day |
| Search queries/ngày | 5,000,000 (5M) | 50x reservation volume |
| Average booking duration | 2.5 đêm | Business + leisure mix |
| Peak-to-average ratio | 10x | Tết, Noel, summer |
| Average reservation payload | 1 KB | JSON with guest info |
| Average search payload | 2 KB | Response 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
| Metric | Value |
|---|---|
| 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-System và Tuan-15-Data-Security-Encryption.
| Nguyên tắc | Áp dụng cho Hotel Reservation |
|---|---|
| KHÔNG lưu card number | Dùng PSP (Stripe, VNPay) hosted payment page. Chỉ lưu token |
| Tokenization | Card 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 reduction | Dùng hosted payment page → giảm PCI-DSS compliance scope |
Prevent Price Manipulation
| Tấn công | Mô tả | Phòng chống |
|---|---|---|
| Price tampering | User 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ăng | Lock giá tại thời điểm tạo reservation, giá được tính trên server |
| Coupon abuse | Dùng coupon nhiều lần hoặc coupon của người khác | Unique coupon per user, server-side validation, rate limiting |
Price validation flow:
| Bước | Mô tả |
|---|---|
| 1 | Client gửi: {hotel_id, room_type_id, dates} — KHÔNG gửi price |
| 2 | Server tính giá từ Rate Service: base_price x nights x tax |
| 3 | Server trả về giá chính thức cho client hiển thị |
| 4 | Client confirm → Server tính giá LẠI lần nữa trước khi charge |
| 5 | Nế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ông | Mô tả | Phòng chống |
|---|---|---|
| Inventory scraping | Bot crawl tất cả availability để bán lại (OTA arbitrage) | Rate limiting: 100 requests/phút/IP |
| Search abuse | Bot search liên tục để theo dõi giá | CAPTCHA sau 50 searches, rate limit per user |
| Reservation bot | Bot tự động đặt phòng khi thấy giá thấp | Device fingerprinting, behavioral analysis |
| DDoS | Tấn công làm sập hệ thống | WAF (Web Application Firewall), CDN protection (Cloudflare) |
Rate limiting strategy — tham khảo Tuan-09-Rate-Limiter:
| Endpoint | Rate limit | Lý do |
|---|---|---|
/hotels/search | 100 req/min/IP | Chống scraping |
/hotels/{id}/rooms/availability | 60 req/min/IP | Chống inventory scraping |
/reservations (POST) | 10 req/min/user | Chống booking bot |
/reservations/{id}/cancel | 5 req/min/user | Chống abuse |
User Data Privacy (GDPR / PDPA)
| Dữ liệu | Phân loại | Bảo vệ |
|---|---|---|
| Tên, email, SDT | PII (Personally Identifiable Information) | Encrypt at rest (AES-256), access control |
| Passport/CCCD | Sensitive PII | Encrypt + restricted access, auto-delete sau 30 ngày |
| Payment token | Payment data | PCI-DSS compliant storage |
| Booking history | Personal data | Accessible only by user + authorized staff |
| IP address, device info | Technical data | Log rotation, anonymize after 90 ngày |
| GDPR Right | Implementation |
|---|---|
| Right to access | API cho user xem toàn bộ data của họ |
| Right to deletion | Soft delete + anonymize PII sau retention period |
| Right to portability | Export booking history dạng JSON/CSV |
| Consent management | Explicit opt-in cho marketing emails |
DevOps & Monitoring — Vận hành và giám sát
Key Metrics to Monitor
| Metric | Mô tả | Alert Threshold | Severity |
|---|---|---|---|
| Reservation Success Rate | % đặt phòng thành công (không tính hết phòng) | < 95% → alert | P1 (Critical) |
| Overbooking Rate | % khách bị relocate do overbooking | > 2% → alert | P1 |
| Inventory Accuracy | Inventory trong DB vs thực tế (sau reconciliation) | Discrepancy > 0.1% → alert | P1 |
| Search Latency P99 | Thời gian search 99th percentile | > 500ms → alert | P2 |
| Reservation Latency P99 | Thời gian đặt phòng E2E | > 3s → alert | P2 |
| Payment Failure Rate | % thanh toán thất bại | > 10% → alert | P1 |
| Hold Expiration Rate | % reservation PENDING bị timeout | > 30% → investigate | P3 |
| Cache Hit Rate | % request được serve từ cache | < 80% → investigate | P3 |
| DB Connection Pool | Số connection hiện tại / max | > 80% → alert | P2 |
| Queue Depth | Số message trong Kafka waiting | > 10,000 → alert | P2 |
Dashboard Layout
| Panel | Metrics | Visualization |
|---|---|---|
| Booking Health | Success rate, volume, error breakdown | Time series (last 24h) |
| Inventory | Occupancy rate per city, overbooking events | Heatmap + counter |
| Search Performance | QPS, latency P50/P95/P99 | Line chart + histogram |
| Payment | Success/failure rate, PSP latency | Pie chart + time series |
| Infrastructure | CPU, memory, DB connections, cache hit rate | Gauge + sparkline |
| Business | Revenue, ADR (Average Daily Rate), RevPAR | Counter + trend |
Alerting Rules
| Alert | Condition | Action |
|---|---|---|
| Overbooking detected | Khách check-in > số phòng available | Page on-call, tìm phòng thay thế |
| Payment gateway down | PSP error rate > 50% cho 5 phút | Failover sang PSP backup, page team |
| Inventory discrepancy | DB inventory khác actual > threshold | Freeze booking cho hotel đó, investigate |
| Search degradation | P99 > 2s cho 10 phút | Scale Elasticsearch nodes, check query performance |
| Expired hold spike | Hold expiration rate > 50% trong 1 giờ | Check payment flow, PSP latency |
SLA Monitoring
| SLA | Target | Measurement |
|---|---|---|
| Uptime | 99.99% (52.6 phút downtime/năm) | Synthetic monitoring mỗi 30 giây |
| Search availability | 99.95% | Health check endpoint |
| Booking availability | 99.99% | End-to-end booking test mỗi 5 phút |
| Data durability | 99.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
| # | Insight | Giải thích |
|---|---|---|
| 1 | DB 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 |
| 2 | Overbooking là business decision, không phải bug | Airlines, hotels đều làm việc này. Cấu hình overbooking percentage là feature, không phải defect |
| 3 | Inventory caching là nguy hiểm | Cache stale inventory → overbooking hoặc lost revenue. Inventory nên đọc trực tiếp từ DB với proper indexing |
| 4 | Idempotency ngăn double charge | Không có idempotency → client retry → 2 bookings + 2 charges. reservation_id là idempotency key tự nhiên |
| 5 | Two-phase reservation là bắt buộc | Phả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 |
| 6 | Hold expiration là critical | Không có hold expiration → “ghost bookings” chiếm inventory mãi mãi. Background job release expired holds |
| 7 | Queue là giải pháp cho flash sale | Serialize requests qua queue → xử lý tuần tự → không concurrency issue. Trade-off: async UX |
| 8 | Saga pattern cho distributed transaction | Reserve → Pay → Confirm. Mỗi bước có compensating action. Không dùng 2PC vì blocking và fragile |
Common Pitfalls
| # | Pitfall | Hậu quả | Cách tránh |
|---|---|---|---|
| 1 | Cache inventory và hiển thị như source of truth | User 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 |
| 2 | Không có hold expiration | User tạo reservation nhưng không thanh toán → phòng bị “giữ” mãi | Background job release PENDING reservations sau 10-15 phút |
| 3 | Tin giá từ client | Attacker sửa giá 5,000,000 VND → 500 VND | Luôn tính giá trên server, KHÔNG tin request body |
| 4 | Không có idempotency key | Double booking, double charge khi client retry | Frontend generate reservation_id trước khi gửi request |
| 5 | Lock quá nhiều rows cùng lúc | Deadlock khi 2 transactions lock các rows theo thứ tự khác nhau | Lock theo thứ tự cố định (sort by date), hoặc dùng optimistic locking |
| 6 | Single point of failure ở Inventory Service | Inventory Service die → toàn bộ booking die | Multi-instance + DB replication + circuit breaker |
| 7 | Không có Saga compensating action | Payment fail nhưng inventory vẫn bị hold | Mỗi forward action phải có compensating action tương ứng |
| 8 | Không partition inventory table | 36.5M records → query chậm | Partition theo date (monthly), chỉ query tương lai |
Interview Tips
| Câu hỏi interviewer có thể hỏi | Cá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 |
Internal Links
| Link | Liên quan |
|---|---|
| Tuan-07-Database-Sharding-Replication | Inventory table partitioning, DB replication cho read scalability |
| Tuan-11-Microservices-Pattern | Saga pattern, service decomposition, inter-service communication |
| Case-Design-Payment-System | Payment flow chi tiết, PCI-DSS, idempotency pattern |
| Tuan-08-Message-Queue | Kafka cho async processing, queue-based reservation |
| Tuan-06-Cache-Strategy | Cache-aside pattern cho hotel data, tại sao không cache inventory |
| Tuan-09-Rate-Limiter | Rate limiting chống scraping và booking bot |
| Tuan-14-AuthN-AuthZ-Security | Authentication, authorization cho admin và user |
| Tuan-15-Data-Security-Encryption | Encryption at rest/transit, PII protection |
| Tuan-02-Back-of-the-envelope | Capacity estimation methodology |
| Tuan-13-Monitoring-Observability | Monitoring, 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.