Skip to Content
Nextra 4.0 is released 🎉
DocumentationHotel Reservation Systemডিজাইন ডিপ ডাইভ (Design Deep Dive)

ডিজাইন ডিপ ডাইভ (Design Deep Dive)

এখন আমরা হাই-লেভেল ডিজাইন নিয়ে আলোচনা করেছি, এবার নিচের বিষয়গুলো আরও গভীরভাবে দেখব:

  • উন্নত ডেটা মডেল (Improved Data Model)
  • কনকারেন্সি সমস্যা (Concurrency Issues)
  • সিস্টেম স্কেলিং (Scaling the System)
  • মাইক্রোসার্ভিস আর্কিটেকচারে ডেটা অসঙ্গতি সমাধান (Resolving Data Inconsistency in Microservice Architecture)

উন্নত ডেটা মডেল (Improved Data Model)

হাই-লেভেল ডিজাইনে যেমন বলা হয়েছে, যখন আমরা একটি হোটেল রুম রিজার্ভ করি, তখন আমরা আসলে নির্দিষ্ট কোনো রুম নয়, বরং একটি রুম টাইপ (Room Type) রিজার্ভ করি। এই নতুন প্রয়োজনীয়তা সমর্থন করতে API এবং ডাটাবেস স্কিমায় কী পরিবর্তন আনতে হবে?

রিজার্ভেশন API-তে roomID এর পরিবর্তে roomTypeID ব্যবহার করা হবে।

রিজার্ভেশন API:

POST /v1/reservations

Request Parameters:

{ "startDate":"2021-04-28", "endDate":"2021-04-30", "hotelID":"245", "roomTypeID":"12354673389", "roomCount":"3", "reservationID":"13422445" }

আপডেটেড স্কিমা Figure 6-এ দেখানো হয়েছে।

গুরুত্বপূর্ণ টেবিলসমূহ

room

রুম সংক্রান্ত তথ্য সংরক্ষণ করে।

room_type_rate

নির্দিষ্ট রুম টাইপের ভবিষ্যৎ তারিখভিত্তিক মূল্য সংরক্ষণ করে।

reservation

অতিথির রিজার্ভেশন সংক্রান্ত তথ্য সংরক্ষণ করে।

room_type_inventory

হোটেলের রুম ইনভেন্টরি সংরক্ষণ করে। এটি রিজার্ভেশন সিস্টেমের সবচেয়ে গুরুত্বপূর্ণ টেবিলগুলোর একটি।

কলামসমূহ:

  • hotel_id → হোটেলের আইডি
  • room_type_id → রুম টাইপের আইডি
  • date → নির্দিষ্ট একটি তারিখ
  • total_inventory → মোট উপলব্ধ রুম সংখ্যা (মেইনটেন্যান্সের জন্য সরিয়ে রাখা রুম বাদ দিয়ে)
  • total_reserved → নির্দিষ্ট হোটেল, রুম টাইপ এবং তারিখের জন্য বুক হওয়া রুম সংখ্যা

(hotel_id, room_type_id, date) একটি Composite Primary Key হিসেবে ব্যবহৃত হয়।

এই টেবিলের ডেটা ২ বছরের ভবিষ্যৎ তারিখ পর্যন্ত আগে থেকেই Pre-populate করা থাকে। প্রতিদিন একটি Scheduled Job চালিয়ে নতুন তারিখের জন্য ডেটা যুক্ত করা হয়।

স্টোরেজ হিসাব

ধরি:

  • ৫,০০০টি হোটেল
  • প্রতি হোটেলে ২০টি রুম টাইপ
  • ২ বছরের ডেটা

তাহলে মোট Row সংখ্যা হবে:

5000 × 20 × 2 × 365 = 73,000,000 rows

৭৩ মিলিয়ন Row খুব বড় নয় এবং একটি ডাটাবেসে সংরক্ষণ করা সম্ভব।

তবে High Availability নিশ্চিত করার জন্য একাধিক Region বা Availability Zone-এ Database Replication ব্যবহার করা যেতে পারে।

room_type_inventory এর নমুনা ডেটা

hotel_idroom_type_iddatetotal_inventorytotal_reserved
21110012021-06-0110080
21110012021-06-0210082
21110012021-06-0310086
2111001
21110012023-05-311000
21110022021-06-0120016
22101012021-06-013023
22101012021-06-023025

রুম অ্যাভেইলেবিলিটি চেক

Input:

startDate = 2021-07-01 endDate = 2021-07-03 roomTypeId hotelId numberOfRoomsToReserve

Output:

True -> রুম পাওয়া যাবে False -> রুম পাওয়া যাবে না

SQL Query:

SELECT date, total_inventory, total_reserved FROM room_type_inventory WHERE room_type_id = ${roomTypeId} AND hotel_id = ${hotelId} AND date BETWEEN ${startDate} AND ${endDate}

রিটার্ন:

datetotal_inventorytotal_reserved
2021-07-0110097
2021-07-0210096
2021-07-0310095

অ্যাপ্লিকেশন যাচাই করবে:

if ( total_reserved + numberOfRoomsToReserve <= total_inventory )

সব তারিখে শর্ত সত্য হলে রুম বুক করা যাবে।

10% Overbooking Support

if ( total_reserved + numberOfRoomsToReserve <= 1.1 * total_inventory )

এর মাধ্যমে ১০% অতিরিক্ত বুকিং অনুমোদন করা সম্ভব।

যদি ডেটা খুব বড় হয়ে যায়?

সম্ভাব্য সমাধান:

1. পুরনো রিজার্ভেশন Archive করা

বর্তমান ও ভবিষ্যতের ডেটা মূল ডাটাবেসে রাখা হবে।

পুরনো ডেটা Archive বা Cold Storage-এ পাঠানো হবে।

2. Database Sharding

যেহেতু অধিকাংশ Query-তে hotel_id ব্যবহৃত হয়, তাই Sharding Key হিসেবে hotel_id ব্যবহার করা যায়।

hash(hotel_id) % number_of_servers

কনকারেন্সি সমস্যা (Concurrency Issues)

Double Booking প্রতিরোধ করতে হবে।

দুই ধরনের সমস্যা হতে পারে:

  1. একই ব্যবহারকারী একাধিকবার Book বাটনে ক্লিক করে।
  2. একাধিক ব্যবহারকারী একই সময়ে একই রুম বুক করতে চায়।

Scenario 1: একই ব্যবহারকারীর একাধিক ক্লিক

ধরি ব্যবহারকারী দুইবার Book বাটনে ক্লিক করেছে।

ফলে একই রিজার্ভেশন দুইবার Insert হতে পারে।

সমাধান ১: Client-side Protection

Request পাঠানোর সাথে সাথে:

  • Submit Button Disable করা
  • Button Gray Out করা
  • Button Hide করা

তবে JavaScript Disable থাকলে এটি কাজ নাও করতে পারে।


সমাধান ২: Idempotent API

Request-এর সাথে একটি Idempotency Key পাঠানো হয়।

এখানে:

reservation_id

Idempotency Key হিসেবে ব্যবহার করা হয়েছে।

ধাপসমূহ

Step 1

রিজার্ভেশন অর্ডার তৈরি করা।

Step 2

সিস্টেম একটি Unique reservation_id জেনারেট করে।

Step 3a

প্রথম Request সফলভাবে Insert হয়।

Step 3b

দ্বিতীয় Request একই reservation_id ব্যবহার করায় Unique Constraint Violation হয়।

ফলে দ্বিতীয় Insert ব্যর্থ হয়।


Scenario 2: একাধিক ব্যবহারকারী একই রুম বুক করছে

ধরি:

total_inventory = 100 total_reserved = 99

মাত্র ১টি রুম বাকি।

User 1 এবং User 2 একই সময়ে বুক করতে চায়।

দুজনেই:

99 + 1 <= 100

দেখে True পায়।

ফলে দুজনেই বুক করে ফেলে।

এটাই Race Condition।


রুম রিজার্ভ করার SQL

Step 1

SELECT date,total_inventory,total_reserved FROM room_type_inventory WHERE room_type_id = ${roomTypeId} AND hotel_id = ${hotelId} AND date BETWEEN ${startDate} AND ${endDate}

Validation

if ( total_reserved + numberOfRoomsToReserve > 1.1 * total_inventory ){ rollback; }

Step 2

UPDATE room_type_inventory SET total_reserved = total_reserved + ${numberOfRoomsToReserve} WHERE room_type_id = ${roomTypeId} AND date BETWEEN ${startDate} AND ${endDate}
COMMIT

Option 1: Pessimistic Locking

এটিকে Pessimistic Concurrency Control-ও বলা হয়।

একজন User Row Update শুরু করলে Row Lock হয়ে যায়।

অন্য User-দের অপেক্ষা করতে হয়।

MySQL উদাহরণ:

SELECT ... FOR UPDATE

সুবিধা

  • Concurrent Update প্রতিরোধ করে
  • Implement করা সহজ
  • Heavy Contention-এ কার্যকর

অসুবিধা

  • Deadlock হতে পারে
  • Scalability খারাপ
  • দীর্ঘ Transaction Performance কমিয়ে দেয়

তাই Hotel Reservation System-এর জন্য এটি সাধারণত সুপারিশ করা হয় না।


Option 2: Optimistic Locking

Optimistic Concurrency Control নামে পরিচিত।

এখানে Database Lock ব্যবহার করা হয় না।

একটি অতিরিক্ত Column:

version

ব্যবহার করা হয়।

প্রক্রিয়া

  1. Version Read
  2. Update
  3. Version + 1
  4. Validation Check

যদি Version Match না করে:

Transaction Abort Retry

সুবিধা

  • Lock লাগে না
  • Stale Data Update প্রতিরোধ করে
  • Low Contention-এ দ্রুত

অসুবিধা

  • High Contention-এ অনেক Retry হয়
  • User Experience খারাপ হতে পারে

তবুও Hotel Reservation System-এর জন্য এটি ভালো সমাধান।


Option 3: Database Constraint

Constraint:

CONSTRAINT check_room_count CHECK ( total_inventory - total_reserved >= 0 )

যদি:

total_inventory = 100 total_reserved = 101

হয়ে যায়, Constraint Violation হবে এবং Transaction Rollback হবে।

সুবিধা

  • Implement করা সহজ
  • Low Contention-এ ভালো কাজ করে

অসুবিধা

  • High Contention-এ অনেক Failure হয়
  • Constraint Version Control কঠিন
  • সব Database Constraint Support করে না

Hotel Reservation System-এর জন্য এটিও একটি বাস্তবসম্মত সমাধান।


Scalability

সাধারণত Hotel Reservation System-এর Load খুব বেশি নয়।

কিন্তু Booking.com বা Expedia-এর মতো বড় সিস্টেমে QPS 1000 গুণ বেশি হতে পারে।

সব Service Stateless হওয়ায় Horizontal Scaling সহজ।

কিন্তু Database Stateful হওয়ায় সেটিই Bottleneck হয়।


Database Sharding

ডেটাকে একাধিক Database-এ ভাগ করা হয়।

Sharding Key:

hotel_id

Routing:

hotel_id % 16

যদি মোট QPS:

30,000

হয়,

তাহলে ১৬টি Shard ব্যবহার করলে:

30,000 / 16 = 1,875 QPS প্রতি Shard

যা একটি MySQL Server সহজেই Handle করতে পারে।


Caching

Hotel Inventory-এর একটি বিশেষ বৈশিষ্ট্য:

শুধুমাত্র বর্তমান ও ভবিষ্যতের Inventory গুরুত্বপূর্ণ।

Redis একটি ভালো পছন্দ কারণ:

  • TTL Support করে
  • LRU Eviction Support করে

Cache Layer

Cache Database-এর উপরে বসানো হয়।

অধিকাংশ Read Request Cache থেকে Serve হয়।


Inventory Cache Structure

Key: hotelID_roomTypeID_date Value: Available Room Count

Inventory APIs

Query Inventory

নির্দিষ্ট Hotel, Room Type এবং Date Range-এর Available Room সংখ্যা ফেরত দেয়।

Reserve Room

total_reserved + 1

Cancel Reservation

Inventory Update করে।


Cache Consistency Challenge

Booking-এর সময়:

Step 1

Inventory Cache থেকে Availability Check।

Step 2

Database Update।

Step 3

Asynchronously Cache Update।

CDC (Change Data Capture) ব্যবহার করে Database → Redis Sync করা যায়।

যেমন:

  • Debezium
  • Kafka Connect

Cache এবং Database অসঙ্গতি

Cache বলছে:

1 room available

Database বলছে:

0 room available

User বুক করতে গেলে Database Final Validation করবে।

তাই Database-ই Source of Truth।


সুবিধা

  • Database Load কমে
  • Read Performance অনেক বেড়ে যায়

অসুবিধা

  • Cache Consistency বজায় রাখা কঠিন
  • User Experience প্রভাবিত হতে পারে

মাইক্রোসার্ভিসে ডেটা কনসিস্টেন্সি

Monolithic Architecture-এ:

একটি Shared Database একটি Transaction ACID Guarantee

Microservice Architecture-এ:

প্রতিটি Service-এর নিজস্ব Database

ফলে:

একটি Logical Transaction একাধিক Database জুড়ে ছড়িয়ে যায়

এবং Data Consistency Challenge তৈরি হয়।


Two-Phase Commit (2PC)

Multiple Node-এ Atomic Commit নিশ্চিত করে।

সব সফল অথবা সব ব্যর্থ

সমস্যা

  • Blocking Protocol
  • Node Failure হলে পুরো Transaction আটকে যায়
  • Performance কম

Saga Pattern

Saga হলো Local Transaction-এর একটি ধারাবাহিকতা।

প্রতিটি Transaction:

  1. Update করে
  2. Event Publish করে
  3. পরবর্তী Service Trigger করে

কোনো Step ব্যর্থ হলে:

Compensating Transaction

চালিয়ে পূর্ববর্তী পরিবর্তন Undo করা হয়।

পার্থক্য

2PC:

Single ACID Transaction

Saga:

Multiple Transactions Eventual Consistency

Microservice-এ Data Consistency বজায় রাখা অত্যন্ত জটিল।

এই ডিজাইনে Pragmatic Approach হিসেবে Reservation Data এবং Inventory Data একই Relational Database-এ রাখা হয়েছে, যাতে ACID Property ব্যবহার করে Concurrency এবং Consistency সমস্যা সহজে সমাধান করা যায়।