System Design শেখো
সব কেস স্টাডি

Ticketmaster (Booking System) ডিজাইন

15 মিনিটadvanced
এক নজরে
  • সীমিত seat inventory নিয়ে concurrent booking — মূল চ্যালেঞ্জ হলো এক seat যেন দুজনকে বিক্রি না হয় (double-booking)।
  • seat locking/reservation timeout দিয়ে অস্থায়ীভাবে seat ধরে রাখা হয়, তারপর payment হলে confirm করা হয়।
  • জনপ্রিয় ইভেন্টে হঠাৎ traffic spike সামলাতে virtual waiting queue ও database-level locking দরকার।

Ticketmaster-এর মতো booking system বানানো interview-এর অন্যতম প্রিয় প্রশ্ন, কারণ এখানে একটাই কঠিন সমস্যা বারবার ফিরে আসে — এক seat যেন দুজনকে বিক্রি না হয়। কনসার্টের টিকিট ছাড়ার মুহূর্তে লাখো মানুষ একসাথে একই ভালো seat-এ ক্লিক করে। চলো ধাপে ধাপে ডিজাইন করি।

১. সমস্যা বোঝা (Requirements)

Functional requirements:

  • ইউজার event খুঁজবে এবং venue-এর seat map দেখবে (কোন seat খালি, কোনটা বুকড)।
  • নির্দিষ্ট seat select করে অস্থায়ীভাবে hold/reserve করতে পারবে।
  • নির্দিষ্ট সময়ের (যেমন ১০ মিনিট) মধ্যে payment করে booking confirm করবে।
  • payment না করলে seat আবার available হয়ে যাবে।

Non-functional requirements:

  • Strong consistency on seats — এটাই সবচেয়ে গুরুত্বপূর্ণ। double-booking একদম অগ্রহণযোগ্য।
  • High availability — search/browse পথ সবসময় চালু।
  • Traffic spike সামলানো — জনপ্রিয় event-এ স্বাভাবিকের শত গুণ traffic।
  • payment idempotent ও নির্ভরযোগ্য হতে হবে।

এখানে CAP-এর দিক থেকে seat booking-এ আমরা consistency-কে availability-র উপরে রাখব। দুজনকে এক seat দেওয়ার চেয়ে কখনো কখনো "try again" দেখানো ভালো।

সহজ উদাহরণ

ব্যাপারটা ঈদের সময় বাসের কাউন্টারের মতো। একটা সিট দুজন দাবি করলে কাউন্টারের লোক (database) একটাই টিকিট ছাপাবে — যে আগে টাকা দেয় সে পায়। আর কেউ সিট ধরে রেখে টাকা না দিয়ে দাঁড়িয়ে থাকলে কিছুক্ষণ পর কাউন্টার বলে "ভাই, সময় শেষ, সিট ছেড়ে দিন" — এটাই reservation timeout।

২. স্কেল আন্দাজ (Estimation)

মোট registered user        = 100 million
সাধারণ দিনে booking         ≈ 5 million/day → ~60 booking/sec (নগণ্য)

কিন্তু আসল চাপ popular event drop-এ:
  একটি stadium event         = 50,000 seat
  drop-এর মুহূর্তে আগ্রহী user = 2 million একসাথে
  drop-এর প্রথম মিনিটে QPS    ≈ লাখো read + হাজারো write
  =>  50,000 seat-এর জন্য 2M user প্রতিযোগিতা (40:1)

read : write
  seat map browsing (read)  বিশাল
  actual reservation (write) ছোট কিন্তু চরম contended

storage (নগণ্য):
  event = কয়েক লক্ষ; প্রতি event seat = হাজার;
  booking row ≈ কয়েক GB/বছর

মূল শিক্ষা: total storage বা গড় QPS সমস্যা না — একটি জনপ্রিয় event-এর সীমিত seat-এ চরম concurrent write contention-ই আসল challenge।

৩. API ডিজাইন

GET  /v1/events/{id}/seats          # seat map + status

POST /v1/reservations
     { event_id, seat_ids: ["s12","s13"], user_id }
     Header: Idempotency-Key: <uuid>
→ 201 { reservation_id, expires_at }   # 10 min hold
→ 409 Conflict                          # seat already taken

POST /v1/reservations/{id}/pay
     { payment_token }
     Header: Idempotency-Key: <uuid>
→ 200 { booking_id, status: "CONFIRMED" }

DELETE /v1/reservations/{id}            # user cancels hold

দুই ধাপ আলাদা রাখা হয়েছে — আগে reserve (seat hold), পরে pay (confirm)। দুটোতেই Idempotency-Key আছে যাতে network retry-তে double-action না ঘটে।

৪. ডেটা মডেল

টেবিলমূল কলামনোট
eventsid, name, venue_id, start_timeevent তথ্য
seatsid, event_id, section, row, status, version, held_by, hold_expires_atপ্রতিটি seat-এর state
reservationsid, user_id, event_id, status, expires_atঅস্থায়ী hold
bookingsid, reservation_id, payment_id, statusনিশ্চিত booking

seats.status enum: AVAILABLE → HELD → BOOKED (অথবা hold expire হলে HELD → AVAILABLE)।

Choice — database? এখানে strong consistency ও ACID transaction লাগবে, তাই relational DB (PostgreSQL/MySQL) নেওয়া হলো। NoSQL eventual consistency double-booking-এর ঝুঁকি বাড়ায়, তাই seat inventory-র জন্য উপযুক্ত নয়।

৫. হাই-লেভেল ডিজাইন

components:

  • API Gateway + Load Balancer
  • Search/Browse Service (read-heavy, cache + replica)
  • Booking Service (seat reservation logic — heart of system)
  • Payment Service (3rd party gateway-র সাথে integration)
  • Inventory DB (seats, transactional, strongly consistent)
  • Reservation expiry worker (timeout হলে seat release)
  • Virtual Queue / Rate Limiter (spike সামলাতে)

Booking flow:

  1. ইউজার seat map দেখে seat select করে।
  2. Booking Service একটি transaction-এ ওই seat-গুলো AVAILABLE → HELD করার চেষ্টা করে।
  3. সফল হলে reservation row তৈরি, expires_at = now + 10 min
  4. ইউজার payment করে; success হলে seat HELD → BOOKED, booking confirmed।
  5. payment না হলে expiry worker HELD → AVAILABLE করে দেয়।

৬. গভীরে (Deep Dive)

Concurrency: double-booking কীভাবে ঠেকাবো

এটাই পুরো সিস্টেমের প্রাণ। দুটি প্রধান পদ্ধতি:

ক) Pessimistic locking — transaction-এ row lock নিয়ে নাও:

BEGIN;
SELECT * FROM seats
  WHERE id = 's12' AND status = 'AVAILABLE'
  FOR UPDATE;          -- row lock
-- যদি row পাওয়া যায় তবেই:
UPDATE seats SET status='HELD', held_by=:user,
       hold_expires_at = now() + interval '10 min'
  WHERE id = 's12';
COMMIT;

FOR UPDATE ওই row lock করে; দ্বিতীয় request প্রথমটি শেষ না হওয়া পর্যন্ত wait করে, তারপর দেখে seat আর AVAILABLE নেই — fail। নিশ্চিত, কিন্তু high contention-এ lock-এর জন্য throughput কমে।

খ) Optimistic locking — version দিয়ে conditional update:

UPDATE seats
   SET status='HELD', held_by=:user, version = version + 1
 WHERE id='s12' AND status='AVAILABLE' AND version = :v;
-- affected rows == 1 হলে সফল, == 0 হলে অন্য কেউ আগে নিয়েছে

এখানে lock-এ wait করতে হয় না; update-এর WHERE status='AVAILABLE' শর্তটাই atomic ভাবে নিশ্চিত করে একটিই request জেতে। contention খুব বেশি না হলে এটা দ্রুততর।

সাবধান

শুধু "আগে check করো, তারপর update করো" (read-then-write) করলে race condition হবে — দুই request একসাথে seat-কে AVAILABLE দেখে দুজনই HELD করে ফেলবে, double-booking হবে। check আর update একই atomic operation-এ থাকতে হবে: হয় SELECT ... FOR UPDATE, নয়তো UPDATE ... WHERE status='AVAILABLE'। মাঝখানে application code-এ গ্যাপ রাখা যাবে না।

Reservation timeout

seat hold করে কেউ payment না করলে seat চিরকাল আটকে থাকবে — তাই timeout। দুই উপায়:

  1. Expiry worker / cron: প্রতি কয়েক সেকেন্ডে hold_expires_at < now() AND status='HELD' খুঁজে AVAILABLE করে।
  2. Lazy expiry: নতুন reservation-এর সময় চেক করি hold expire হয়েছে কিনা — তখনই release। (worker-এর সাথে combine করলে ভালো।)

Redis-এ key TTL দিয়েও hold রাখা যায় (key expire হলেই seat free), কিন্তু source of truth DB-তেই রাখা নিরাপদ যাতে Redis-DB inconsistency-তে double-booking না হয়।

Payment flow ও idempotency

payment external gateway-তে যায় — slow ও unreliable। তাই:

  • reservation hold আগে নিয়ে নেওয়া হয়, তারপর payment — payment-এর সময়ও seat নিরাপদ।
  • Idempotency-Key দিয়ে retry-তে double charge ঠেকানো হয়। gateway থেকে success এলে seat BOOKED; timeout/fail হলে hold expire হয়ে seat ফিরে আসে।
  • payment confirm হওয়ার পরই booking final — eventual confirmation webhook handle করতে হবে।

Traffic spike: Virtual Queue

জনপ্রিয় event drop-এ লাখো user একসাথে আসে। সবাইকে সরাসরি Booking Service-এ ঢুকতে দিলে DB ধসে পড়বে। সমাধান — virtual waiting room/queue:

  • drop শুরু হলে সবাই একটা queue-তে যায়, প্রত্যেক পায় একটা token ও position।
  • সিস্টেম নিয়ন্ত্রিত হারে (rate limit) ব্যাচে ব্যাচে user-দের booking page-এ ঢুকতে দেয়।
  • এতে DB-র উপর চাপ স্থির থাকে এবং fair ordering বজায় থাকে।

৭. বটলনেক ও স্কেলিং

  • Hot inventory row: এক জনপ্রিয় event-এর কয়েকটি ভালো seat-এ সব contention জমে। সমাধান — virtual queue দিয়ে inflow নিয়ন্ত্রণ, এবং seat-level (row-level) lock যাতে পুরো event lock না হয়।
  • Read scaling: seat map browsing read replica + cache দিয়ে, কিন্তু seat status যেহেতু দ্রুত বদলায়, cache short TTL বা real-time push (WebSocket) দিয়ে আপডেট রাখা ভালো।
  • DB write scaling: event অনুযায়ী sharding (এক event-এর seat এক shard-এ) — তাহলে এক event-এর contention অন্য event-কে প্রভাবিত করে না।
  • Distributed lock: multi-instance-এ একই seat handle করতে হলে DB transaction-ই যথেষ্ট; প্রয়োজনে Redis-ভিত্তিক distributed lock, তবে DB-ই চূড়ান্ত source of truth।
  • Idempotency store: key + result আলাদা টেবিল/Redis-এ রাখা যাতে retry safe।

৮. সারসংক্ষেপ

Booking system-এর পুরো গল্পটাই ঘোরে inventory-র উপর strong consistency নিয়ে — double-booking ঠেকাতে check-and-update একটি atomic database operation-এ করতে হবে (pessimistic বা optimistic lock)। দুই ধাপের flow (reserve তারপর pay) + reservation timeout নিশ্চিত করে seat আটকে না থেকে ন্যায্যভাবে বণ্টিত হয়। payment-এ idempotency, আর spike সামলাতে virtual queue — এগুলোই production-ready বানায়।

টিপস

Interview-এ double-booking ঠেকানোর সময় স্পষ্ট করে বলো check ও update একই atomic statement-এ হবে, এবং pessimistic বনাম optimistic locking-এর trade-off (contention কম হলে optimistic, বেশি হলে pessimistic) ব্যাখ্যা করো — এটাই signal দেয় যে তুমি concurrency বোঝো।

মিনি কুইজ

1. দুজন user একই seat বুক করার চেষ্টা করলে double-booking ঠেকাতে সবচেয়ে নির্ভরযোগ্য উপায় কোনটি?

2. Seat reservation-এ timeout কেন রাখা হয়?

3. Payment API-তে idempotency key কেন জরুরি?