Ticketmaster (Booking System) ডিজাইন
- ●সীমিত 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 না ঘটে।
৪. ডেটা মডেল
| টেবিল | মূল কলাম | নোট |
|---|---|---|
events | id, name, venue_id, start_time | event তথ্য |
seats | id, event_id, section, row, status, version, held_by, hold_expires_at | প্রতিটি seat-এর state |
reservations | id, user_id, event_id, status, expires_at | অস্থায়ী hold |
bookings | id, 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:
- ইউজার seat map দেখে seat select করে।
- Booking Service একটি transaction-এ ওই seat-গুলো
AVAILABLE → HELDকরার চেষ্টা করে। - সফল হলে reservation row তৈরি,
expires_at = now + 10 min। - ইউজার payment করে; success হলে seat
HELD → BOOKED, booking confirmed। - 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। দুই উপায়:
- Expiry worker / cron: প্রতি কয়েক সেকেন্ডে
hold_expires_at < now() AND status='HELD'খুঁজেAVAILABLEকরে। - 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 এলে seatBOOKED; 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 কেন জরুরি?