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

Notification System ডিজাইন

14 মিনিটintermediate
এক নজরে
  • একটি notification system push, email আর SMS—এই তিন channel-এ লক্ষ লক্ষ মানুষকে message পাঠায়।
  • Message queue + workers দিয়ে fan-out করা হয়, যাতে slow third-party provider পুরো system আটকে না দেয়।
  • User preference, rate limiting, retry + dead-letter queue আর deduplication—এই চারটা জিনিস ছাড়া system বিরক্তিকর আর অবিশ্বাস্য হয়ে যায়।

ধরো তুমি bKash-এর মতো একটা অ্যাপ বানাচ্ছো। প্রতিবার টাকা পাঠালে ব্যবহারকারীর কাছে একটা push notification, একটা SMS, আর হয়তো একটা email যায়। লক্ষ লক্ষ ব্যবহারকারী, প্রতি সেকেন্ডে হাজারো transaction—এই সব notification নির্ভরযোগ্যভাবে পাঠানোটাই একটা পুরো system। চলো এটা ইন্টারভিউ-এর মতো করে ধাপে ধাপে ডিজাইন করি।

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

প্রথমেই ঠিক করি system-টা আসলে কী করবে।

Functional requirements:

  • তিন ধরনের Channel-এ notification পাঠানো: push (মোবাইল অ্যাপ), email, আর SMSChannel মানে যে মাধ্যম দিয়ে message ব্যবহারকারীর কাছে পৌঁছায়।
  • বিভিন্ন service (যেমন payment service, marketing service) একটা সাধারণ API দিয়ে notification পাঠাতে পারবে।
  • Template সমর্থন—একই message এর কাঠামো বারবার লিখতে না হয়।
  • ব্যবহারকারী নিজের preference ঠিক করতে পারবে (যেমন "marketing email বন্ধ", কিন্তু "transaction SMS চালু")।
  • একই notification যেন দুইবার না যায় (deduplication)।

Non-functional requirements:

  • নির্ভরযোগ্যতা (reliability): transaction notification হারানো যাবে না।
  • স্কেলযোগ্যতা (scalability): প্রতি সেকেন্ডে হাজারো notification সামলাতে হবে।
  • Low latency (push/SMS-এর জন্য): transaction-এর পরে কয়েক সেকেন্ডের মধ্যে।
  • Rate limiting: একই user-কে এক ঘণ্টায় ১০০টা notification পাঠিয়ে বিরক্ত করা যাবে না।

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

ইন্টারভিউতে napkin math দেখাতে হয়। ধরা যাক:

DAU (Daily Active Users)      = 1 কোটি (10,000,000)
প্রতি user প্রতিদিন গড়ে        = 5টি notification
মোট notification/দিন          = 10M × 5 = 50M (৫ কোটি)

QPS (গড়)   = 50,000,000 / 86,400 sec ≈ 580 notifications/sec
QPS (peak)  = গড়ের ~3x ≈ 1,740/sec

Channel ভাগ (আনুমানিক):
  Push  = 70%  → ~35M/দিন
  Email = 20%  → ~10M/দিন
  SMS   = 10%  → ~5M/দিন

Storage (notification log, 90 দিন রাখলে):
  প্রতি record ≈ 500 bytes
  50M/দিন × 500 B = 25 GB/দিন
  90 দিন = ~2.25 TB

মূল কথা: peak QPS ~১,৭০০ তেমন বড় না, কিন্তু spike ভয়ংকর। যেমন কোনো বড় campaign বা OTP-এর ঢল এলে হঠাৎ ১০ গুণ চাপ আসতে পারে। তাই queue দিয়ে চাপ সামলানো জরুরি।

৩. API ডিজাইন

একটাই সহজ entry point রাখি, যাতে সব service একই API ডাকে।

POST /v1/notifications
{
  "user_id": "u_12345",
  "type": "transaction_success",   // template নির্ধারণ করে
  "channels": ["push", "sms"],
  "data": { "amount": "5000", "name": "করিম" },
  "idempotency_key": "txn_9876_notif"  // duplicate ঠেকাতে
}
→ 202 Accepted { "request_id": "req_abc" }

GET  /v1/notifications/{request_id}     // status দেখা
PUT  /v1/users/{user_id}/preferences    // opt-in/opt-out

লক্ষ করো 202 Accepted ফেরত দিচ্ছি, 200 OK নয়। কারণ আমরা notification সাথে সাথে পাঠাচ্ছি না—শুধু "কাজটা নিয়েছি" বলছি। আসল পাঠানো background-এ হবে।

৪. ডেটা মডেল

মূল table/collection গুলো:

Tableপ্রধান fieldকাজ
notificationsrequest_id, user_id, type, status, idempotency_key, created_atপ্রতিটি request-এর log
templatestemplate_id, channel, subject, body_with_placeholdersmessage-এর কাঠামো
user_preferencesuser_id, channel, type, enabledকে কোন notification চায়
device_tokensuser_id, platform, tokenpush পাঠানোর APNs/FCM token

SQL নাকি NoSQL? এখানে mixed approach ভালো:

  • user_preferences, templates, device_tokensSQL (PostgreSQL)। কারণ এগুলোর সম্পর্ক স্পষ্ট, পরিমাণ সীমিত, আর consistency দরকার।
  • notifications log → NoSQL (Cassandra)। কারণ এটা প্রচুর write হয় (৫ কোটি/দিন), শুধু time-series log, আর complex join লাগে না। Cassandra এই write-heavy কাজে দারুণ।
সহজ উদাহরণ

notification system-টা ভাবো একটা বড় কুরিয়ার অফিসের মতো। তুমি প্যাকেট (notification) জমা দাও, রসিদ (request_id) পাও, আর চলে যাও। ভেতরে কর্মীরা (workers) প্যাকেটগুলো বাছাই করে—কোনটা বাইকে (push), কোনটা ডাকে (email), কোনটা বিশেষ চিঠিতে (SMS)। তুমি দাঁড়িয়ে থেকে delivery দেখো না; অফিস নিজের গতিতে কাজ করে।

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

মূল components:

  1. Notification API / Producer — request নেয়, validate করে, queue-তে দেয়।
  2. Message Queue (Kafka বা RabbitMQ) — buffer হিসেবে কাজ করে।
  3. Workers (Consumers) — queue থেকে message নিয়ে আসল পাঠানোর কাজ করে।
  4. Channel adapters — push, email, SMS-এর জন্য আলাদা code।
  5. Third-party providers — APNs/FCM (push), SendGrid (email), Twilio (SMS)।

Request flow ধাপে ধাপে:

  1. Payment service POST /v1/notifications ডাকে।
  2. API প্রথমে idempotency_key চেক করে—আগে এই request এসেছে কিনা। এসে থাকলে আগের request_id ফেরত দেয়।
  3. API user-এর preference দেখে—সে কি এই channel-এ এই type চায়? না চাইলে skip।
  4. বৈধ হলে API প্রতিটি channel-এর জন্য আলাদা message বানিয়ে queue-তে ঢালে (এটাই Fan-out—এক request থেকে একাধিক channel-এর কাজ ছড়িয়ে দেওয়া)।
  5. API এখনই 202 Accepted ফেরত দেয়।
  6. Worker queue থেকে message নেয়, template দিয়ে চূড়ান্ত text বানায়, তারপর সঠিক channel adapter দিয়ে provider-কে পাঠায়।
  7. Provider সফল হলে worker notifications log-এ status = sent লেখে। fail করলে retry-র ব্যবস্থা চালু হয়।

৬. গভীরে (Deep Dive)

Queue ও Workers কেন এত গুরুত্বপূর্ণ

ধরো Twilio (SMS provider) হঠাৎ ২ সেকেন্ড দেরি করছে বা down। যদি API সরাসরি Twilio-কে ডাকত, তাহলে payment service-ও আটকে থাকত, user-এর কাছে "টাকা গেল কিনা" উত্তরই আসত না।

Message Queue (একটা buffer যেখানে কাজ জমা থাকে, পরে process হয়) দিয়ে আমরা producer আর consumer-কে আলাদা (decouple) করি। Provider slow হলে শুধু queue-তে message জমতে থাকে, main service ঠিকঠাক চলে। আলাদা channel-এর জন্য আলাদা queue (push-queue, email-queue, sms-queue) রাখলে এক channel slow হলেও বাকিগুলো আটকায় না।

Retry, Backoff আর Dead-Letter Queue

Provider মাঝে মাঝে temporary error দেবে (network glitch, 503)। তখন worker সাথে সাথে হাল ছাড়বে না—exponential backoff দিয়ে retry করবে: প্রথমে ১ sec পরে, তারপর ২, ৪, ৮ sec... এতে provider-এর উপর চাপ কমে।

কিন্তু কিছু message কখনোই যাবে না (যেমন invalid phone number)। এদের অসীম বার retry করলে queue জ্যাম হবে। তাই ৩-৫ বার fail করলে message-কে Dead-Letter Queue-তে (DLQ—যেখানে "মৃত" message আলাদা করে রাখা হয়) সরিয়ে দিই। পরে engineer DLQ দেখে বুঝবে কী ভুল হয়েছে।

সাবধান

Retry করতে গিয়ে সাবধান—retry মানেই duplicate-এর ঝুঁকি। ধরো worker SMS পাঠিয়েছে, কিন্তু status লেখার আগে crash করল। restart-এর পর সে আবার পাঠাবে! তাই প্রতিটি send operation-কে Idempotency দিয়ে guard করতে হবে—মানে একই key দিয়ে দুইবার ডাকলেও একবারই effect হবে।

Deduplication

দুই জায়গায় dedup দরকার: (১) একই request যেন দুইবার queue-তে না ঢোকে—এর জন্য idempotency_key Redis-এ অল্প সময় cache করে রাখি; (২) worker level-এ—একই message দুইবার process হলেও provider-কে একবারই পাঠানো নিশ্চিত করতে send করার আগে Redis-এ sent:<message_id> চেক করি।

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

  • Worker scaling: queue-তে backlog বাড়লে আরও worker autoscale করে যোগ করো। workers stateless, তাই অনুভূমিকভাবে (horizontally) সহজে বাড়ে।
  • Provider rate limit: Twilio/FCM-এর নিজস্ব rate limit আছে। তাই আমাদের worker-level rate limiter (token bucket) দরকার, নইলে provider আমাদের block করবে।
  • User-level rate limiting: Redis-এ counter রেখে নিশ্চিত করো এক user এক ঘণ্টায় নির্দিষ্ট সংখ্যার বেশি notification না পায়।
  • Hot partition: Kafka-তে যদি partition key খারাপ হয় (যেমন সব message এক partition-এ), তাহলে এক worker-এর উপর সব চাপ পড়বে। user_id দিয়ে partition করলে চাপ ছড়িয়ে যায়।
  • DB write load: notification log Cassandra-তে রাখায় ৫ কোটি/দিন write সামলানো যায়।

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

আমরা একটা notification system বানালাম যেখানে—API request নিয়ে দ্রুত 202 ফেরত দেয়, idempotency আর preference চেক করে, message queue-তে fan-out করে, আর workers ধীরস্থিরভাবে push/email/SMS provider-এ পাঠায়। নির্ভরযোগ্যতার জন্য আছে retry with backoff, dead-letter queue, আর deduplication।

টিপস

ইন্টারভিউয়ার দেখতে চান তুমি কি বুঝেছ কেন queue দরকার (decoupling), কীভাবে failure সামলাবে (retry + DLQ), আর কীভাবে duplicate ঠেকাবে (idempotency)। শুধু "Kafka ব্যবহার করব" বললে হবে না—কেন আর কীভাবে, সেটা বলতে পারলেই তুমি এগিয়ে।

মিনি কুইজ

1. Notification system-এ message queue ব্যবহার করার প্রধান কারণ কী?

2. একই notification দুইবার পাঠানো ঠেকাতে কোন কৌশল কাজে লাগে?

3. বারবার fail করা message-এর জন্য dead-letter queue (DLQ) কেন দরকার?