System Design advanced LIVE DEMO
🏨

Hotel Reservation Concurrency

Understand concurrency control in a hotel reservation system. Learn how inventory check, pessimistic locking, optimistic locking, idempotent payments, ledger logging

system-designconcurrencylockingidempotencyredisrdbmscqrs
Interactive Demo

Continue with the guided workspace

Use the paired workspace to connect this guide's mental model with live component state, visible signals, and the outcome each step or scenario is meant to prove.

Designing a hotel reservation system requires addressing the core challenge of double-booking under high concurrency.

Mental Model

When multiple customers try to book the last available room simultaneously, a system with inadequate isolation will suffer from overbooking. The system coordinates booking requests using either optimistic locking (detecting conflicts via version checks) or pessimistic locking (blocking concurrent updates via database row locks). When a reservation is requested, the service checks the cache, acquires a row lock in the transactional database for pessimistic locking (or checks a version for optimistic locking), and creates a pending booking record. The system then processes the payment using an idempotency key to prevent duplicate charges. If the payment succeeds, the transaction commits, updates the read cache, and writes to an audit ledger; if payment fails, the database transaction rolls back, releasing locks and restoring cached inventory.

Components and Why They Exist

Customer Browser

The customer browser is the client-side entry point representing the user searching for rooms and initiating bookings. It submits search queries, reservation requests, and payment payloads to the API gateway, firing parallel checkout requests to expose how concurrency conflicts are resolved.

API Gateway

The API gateway is the single entry point that routes client requests, performs rate limiting, and handles authentication. It directs read queries to the search model (Elasticsearch) and dispatches write checkout requests to the reservation service.

Redis Cache

The Redis cache improves search performance by caching room availability grids using a cache-aside pattern. It is not the source of truth for reservation locking; the database row lock or version check protects the booking write while cached availability counters update after state changes.

Reservation Service

The reservation service coordinates the booking lifecycle, transaction checks, locking strategies, and third-party integrations. It checks the cache, requests database locks, submits payments, logs to audit ledgers, and transitions reservation states across pending, confirmed, or rolled-back outcomes.

Database (RDBMS)

The relational database (RDBMS) is the transactional source of truth for booking records, requiring strict ACID compliance. It implements row locking (SELECT FOR UPDATE for pessimistic locking) and version-based checks (for optimistic locking) to protect inventory rows from double-booking as row lock badges and version counters update.

Elasticsearch (CQRS Read View)

The Elasticsearch index serves as a scalable CQRS read model to handle heavy room-search queries. It stores denormalized room availability documents that bypass the primary database on reads and sync asynchronously via Change Data Capture (CDC) after database commits.

Payment Gateway

The payment gateway handles third-party credit card processing securely. It processes payment charges and requires an idempotency key on every request to prevent duplicate charges across retries, triggering a full reservation rollback whenever a charge is declined.

Audit Ledger

The audit ledger keeps a double-entry transaction record for financial reconciliation. It appends immutable, append-only log entries for all successful and reversed reservations once a booking state is finalized.

Notification Service

The notification service informs users about their booking results asynchronously. It dispatches confirmation emails or push notifications to the customer once the reservation transaction and ledger commit succeed.

Edge Cases and Misconceptions

  • Misconception: Caching availability guarantees double-booking protection: Redis is fast but usually not the transaction source of truth. If Redis cache availability says a room is free, the reservation service must still secure a database lock (optimistic or pessimistic) on the primary database to guarantee liveness under contention.
  • Misconception: Optimistic locking is always superior to pessimistic locking: Optimistic locking is great for low contention because it avoids database lock overhead. However, under high contention, optimistic locking will cause many transactions to fail and retry, degrading performance. Pessimistic locking is better suited for high contention.
  • Edge Case: Payment Success with DB Failures: If payment succeeds but the server crashes before committing the reservation to the database, a severe inconsistency occurs. Idempotent retries and write-ahead logs help ensure the database eventually catches up, or the payment is refunded.

Common Questions

What is the difference between pessimistic and optimistic locking in reservations?

Pessimistic locking locks the database row immediately (e.g., SELECT FOR UPDATE) to block other transactions, suitable for high contention. Optimistic locking checks a version column during update without holding locks, failing the transaction if a concurrent update modified the version first.

Why is an idempotency key critical for payment processing?

If a network error occurs, clients might retry a payment. An idempotency key ensures the payment gateway processes the charge exactly once, returning the cached response for subsequent retries instead of double-charging.

How does the system recover if a payment is declined?

The reservation service rolls back the database transaction, releases any active row locks, updates the Redis cache to restore inventory availability, and returns an error response to the client.

Why is CQRS used in reservation systems?

CQRS separates high-throughput search queries from write transactions. Room searches are directed to Elasticsearch or Redis, while write transactions go directly to the primary relational database, preventing search traffic from overwhelming inventory writes.