পাঠ ২২ · ৫১-এর মধ্যে · মডিউল ৬
Home / Courses / System Design / মেসেজ কিউ ও Pub/Sub

মেসেজ কিউ ও Pub/Sub প্যাটার্ন

Message queues & pub/sub patterns
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • মেসেজ কিউ কী এবং কেন সরাসরি সিঙ্ক্রোনাস কলের বদলে এটি ব্যবহার করা হয়
  • পয়েন্ট-টু-পয়েন্ট কিউ বনাম Pub/Sub-এর পার্থক্য এবং কোনটি কখন ব্যবহার করবেন
  • ডিকাপলিং, লোড লেভেলিং ও রিলায়েবিলিটি — মেসেজ কিউয়ের তিনটি মূল সুবিধা
  • Python দিয়ে একটি বাফারিং কিউ বাস্তবায়ন করে দেখা

১ · মেসেজ কিউ কী এবং কেন দরকার

এতদিন আমরা যেসব সিস্টেম দেখেছি সেগুলোতে একটি সার্ভিস আরেকটি সার্ভিসকে সরাসরি কল করে এবং রেসপন্সের জন্য অপেক্ষা করে (সিঙ্ক্রোনাস)। কিন্তু অনেক কাজ তাৎক্ষণিকভাবে শেষ করার দরকার নেই — যেমন একটি ইমেইল পাঠানো, একটি ভিডিও ট্রান্সকোড করা, বা একটি রিপোর্ট জেনারেট করা। এসব ক্ষেত্রে মেসেজ কিউMessage Queueপ্রোডিউসার ও কনজিউমারের মধ্যে একটি বাফার — প্রোডিউসার বার্তা কিউতে রেখে সাথে সাথে ফিরে যেতে পারে, কনজিউমার নিজের গতিতে সেগুলো প্রসেস করে। কাজে আসে — এটি প্রোডিউসার (যে বার্তা তৈরি করে) ও কনজিউমারের (যে বার্তা প্রসেস করে) মাঝখানে একটি বাফার হিসেবে বসে থাকে।

মূল আইডিয়া

প্রোডিউসার একটি বার্তা কিউতে রেখে সাথে সাথে অন্য কাজে চলে যেতে পারে — কনজিউমারের জবাবের জন্য অপেক্ষা করতে হয় না। কনজিউমার তার নিজের গতিতে, নিজের সুবিধামতো সময়ে কিউ থেকে বার্তা তুলে প্রসেস করে। এই দুই পক্ষ একে অপরের সম্পর্কে কিছুই জানে না — শুধু কিউয়ের ঠিকানা জানে।

২ · পয়েন্ট-টু-পয়েন্ট কিউ বনাম Pub/Sub

মেসেজিং সিস্টেমের দুটি প্রধান প্যাটার্ন আছে, এবং তারা সম্পূর্ণ ভিন্ন সমস্যার সমাধান করে।

পয়েন্ট-টু-পয়েন্ট (কিউ)
প্রতিটি বার্তা ঠিক একজন কনজিউমার প্রসেস করে — একবার প্রসেস হলে বার্তাটি কিউ থেকে চলে যায়। উদাহরণ: RabbitMQ, Amazon SQS। টাস্ক ডিস্ট্রিবিউশনের জন্য আদর্শ (একই কাজ বহুবার না করা)।
Pub/SubPublish/Subscribeএকটি টপিকে প্রকাশিত প্রতিটি বার্তা সেই টপিকের সব সাবস্ক্রাইবার একটি করে কপি পায় — একাধিক স্বাধীন সিস্টেমকে একই ইভেন্ট জানানোর জন্য ব্যবহৃত হয়।
প্রতিটি বার্তা টপিকের সব সাবস্ক্রাইবার একটি করে কপি পায়। উদাহরণ: Kafka, Redis Pub/Sub। একাধিক স্বাধীন সিস্টেমকে একই ইভেন্ট জানানোর জন্য আদর্শ (L23-এ বিস্তারিত)।

সহজ কথায় — কিউ ব্যবহার করুন যখন আপনি চান একটি কাজ ঠিক একবার কেউ একজন করুক (যেমন একটি অর্ডার প্রসেস করা)। Pub/Sub ব্যবহার করুন যখন একই ইভেন্ট নিয়ে একাধিক আলাদা সিস্টেমের স্বাধীনভাবে প্রতিক্রিয়া জানানো দরকার (যেমন একটি অর্ডার প্লেস হলে ইনভেন্টরি, নোটিফিকেশন ও অ্যানালিটিক্স — তিনটি ভিন্ন সিস্টেমই জানতে চায়, বিস্তারিত L23-এ)।

Point-to-point: প্রোডিউসার Producer কিউ Queue শুধু একজন One consumer Pub/Sub: পাবলিশার Publisher টপিক Topic সাবস্ক্রাইবার ১ Subscriber 1 সাবস্ক্রাইবার ২ Subscriber 2 সাবস্ক্রাইবার ৩ Subscriber 3
পয়েন্ট-টু-পয়েন্ট কিউতে একটি বার্তা একজনই পায়; Pub/Sub-এ টপিকের সব সাবস্ক্রাইবার একটি করে কপি পায়।

৩ · মেসেজ কিউয়ের তিনটি মূল সুবিধা

ডিকাপলিং, লোড লেভেলিং, রিলায়েবিলিটি

ডিকাপলিং — প্রোডিউসার ও কনজিউমার একে অপরের ঠিকানা, প্রযুক্তি, এমনকি আপ-টাইম সম্পর্কেও জানার দরকার নেই — শুধু কিউয়ের সাথে কথা বলে। লোড লেভেলিং (বাফারিং) — হঠাৎ ট্রাফিক স্পাইক এলে (যেমন ফ্ল্যাশ সেল) কিউ সব রিকোয়েস্ট জমা রাখে, কনজিউমার তার নিজের স্থিতিশীল গতিতে সেগুলো প্রসেস করে — কনজিউমার সার্ভিস ওভারলোড হয়ে ভেঙে পড়ে না। রিলায়েবিলিটি — বার্তা কিউতে persist থাকে যতক্ষণ না এটি acknowledge করা হয় — কনজিউমার ক্র্যাশ করলেও বার্তা হারিয়ে যায় না, অন্য কনজিউমার ইনস্ট্যান্স বা রিস্টার্টের পর সেটি আবার প্রসেস হয়।

৪ · Python-এ একটি সাধারণ ইন-মেমরি কিউ

নিচে collections.deque দিয়ে একটি ছোট কিউ বানানো হলো। লক্ষ্য করুন — প্রোডিউসার একসাথে কয়েকটি বার্তা পাঠিয়ে দিচ্ছে, কনজিউমার তখনও চালু হয়নি, তবু কোনো বার্তা হারায় না — সবগুলো কিউতে বাফার হয়ে অপেক্ষা করছে।

Python
from collections import deque

queue = deque()

def produce(msg):
    queue.append(msg)
    print(f"producer পাঠালো: {msg} (কিউতে এখন {len(queue)}টি বার্তা)")

def consume():
    if queue:
        msg = queue.popleft()
        print(f"consumer প্রসেস করলো: {msg}")
        return msg
    print("কিউ খালি")
    return None

# প্রোডিউসার বেশ কয়েকটি মেসেজ পাঠাচ্ছে, consumer এখনও একটিও চালায়নি
produce("Order#101 placed")
produce("Order#102 placed")
produce("Order#103 placed")
produce("Order#104 placed")

print(f"\nএখন কিউতে {len(queue)}টি বার্তা জমে আছে (consumer এখনো একটিও প্রসেস করেনি)\n")

# consumer এখন চালু হয়ে একে একে প্রসেস করছে, নিজের গতিতে
while queue:
    consume()

print(f"\nকিউ এখন খালি — অবশিষ্ট বার্তা: {len(queue)}")

    
লক্ষ্য করুন — প্রোডিউসার ৪টি বার্তা পাঠানোর সময় consumer একবারও চালু হয়নি, তবু কোনো বার্তা হারায়নি। এটাই বাফারিং/লোড লেভেলিং-এর মূল প্রদর্শন — বাস্তব সিস্টেমে এই কিউ একটি ব্রোকার (RabbitMQ/SQS/Kafka) দ্বারা ডিস্কে বা রেপ্লিকেটেড ক্লাস্টারে persist করা হয়, যাতে ব্রোকার নিজেই ক্র্যাশ করলেও বার্তা না হারায়।

৫ · কোথায় ব্যবহার হয়

ইমেইল/SMS পাঠানো, ভিডিও ট্রান্সকোডিং (L48), অর্ডার প্রসেসিং পাইপলাইন, লগ অ্যাগ্রিগেশন, এবং মাইক্রোসার্ভিসের মধ্যে অ্যাসিনক্রোনাস কমিউনিকেশন — এসব ক্ষেত্রে মেসেজ কিউ ও Pub/Sub ব্যাপকভাবে ব্যবহৃত হয়। পরবর্তী দুটি পাঠে আমরা দেখব কীভাবে এই বেসিক বিল্ডিং ব্লকের উপর ভিত্তি করে সম্পূর্ণ ইভেন্ট-ড্রিভেন আর্কিটেকচার (L23) এবং স্ট্রিম প্রসেসিং সিস্টেম (L24) বানানো হয়।

মূল কথা · Key takeaway

মেসেজ কিউ হলো "সময়ে ডিকাপলিং" — প্রোডিউসার ও কনজিউমারকে একই সময়ে একসাথে অনলাইন থাকতে হয় না। এটি সিঙ্ক্রোনাস রিকোয়েস্ট-রেসপন্স (যা এতদিন আমরা দেখেছি) থেকে সম্পূর্ণ ভিন্ন একটি কমিউনিকেশন মডেল, এবং অনেক সিস্টেমেই এটি নির্ভরযোগ্যতা ও স্কেলেবিলিটির চাবিকাঠি।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ একটি ই-কমার্স অর্ডার প্লেস করার সময় সরাসরি সিঙ্ক্রোনাস কল না করে মেসেজ কিউ ব্যবহার করলে ইউজার অভিজ্ঞতায় কী পার্থক্য আসে?

সিঙ্ক্রোনাস মডেলে ইউজারকে অপেক্ষা করতে হতো যতক্ষণ না ইনভেন্টরি আপডেট, ইমেইল পাঠানো এবং অ্যানালিটিক্স লগিং — সবগুলো ধাপ শেষ হয়। কিউ ব্যবহার করলে অর্ডার সার্ভিস শুধু একটি বার্তা কিউতে রেখেই ইউজারকে সাথে সাথে "অর্ডার গৃহীত" রেসপন্স দিতে পারে — বাকি ধাপগুলো ব্যাকগ্রাউন্ডে অ্যাসিনক্রোনাসভাবে চলে। ফলে রেসপন্স টাইম অনেক কম, এবং কোনো একটি ডাউনস্ট্রিম সার্ভিস (যেমন ইমেইল সার্ভার) সাময়িকভাবে ডাউন থাকলেও অর্ডার প্লেসমেন্ট ব্যর্থ হয় না — বার্তা কিউতে অপেক্ষা করে, সার্ভিস ফিরে এলে প্রসেস হয়।

প্র ০২ একটি টাস্ক (যেমন পেমেন্ট প্রসেসিং) কি পয়েন্ট-টু-পয়েন্ট কিউ নাকি Pub/Sub দিয়ে করা উচিত — এবং কেন?

পেমেন্ট প্রসেসিং-এর মতো টাস্ক পয়েন্ট-টু-পয়েন্ট কিউ দিয়ে করা উচিত, কারণ একটি নির্দিষ্ট পেমেন্ট ঠিক একবার, ঠিক একজন ওয়ার্কার প্রসেস করবে এটাই কাম্য — একাধিক ওয়ার্কার একই পেমেন্ট দুইবার প্রসেস করলে ডাবল-চার্জিং হয়ে যাবে (L38-এ আইডেম্পোটেন্সি দিয়ে এই ঝুঁকি আরও কমানো হয়)। বিপরীতে, "OrderPlaced" এর মতো একটি ইভেন্ট Pub/Sub দিয়ে করা উচিত কারণ একাধিক স্বাধীন সিস্টেম (ইনভেন্টরি, নোটিফিকেশন, অ্যানালিটিক্স) একই ইভেন্ট নিয়ে স্বাধীনভাবে কাজ করতে চায় — একজন প্রসেস করলে অন্যরা বঞ্চিত হওয়া উচিত না।

প্র ০৩ মেসেজ কিউ থাকা সত্ত্বেও কনজিউমার যদি কখনো চালু না হয়, তাহলে বার্তাগুলোর কী হবে — এবং এর সাথে সিস্টেম ডিজাইনে কী সতর্কতা জড়িত?

বার্তাগুলো কিউতে জমতে থাকবে — যা মেমরি/ডিস্ক ব্যবহার বাড়াবে এবং একটা নির্দিষ্ট সীমার পর কিউ নিজেই ওভারফ্লো বা পারফরম্যান্স সমস্যায় পড়তে পারে। এই কারণে বাস্তব সিস্টেমে কিউয়ের গভীরতা (queue depth) মনিটর করা হয় (L41-এ মনিটরিং দেখব) এবং কনজিউমার ডাউন হলে অ্যালার্ট পাঠানো হয়। অনেক ব্রোকারে মেসেজের একটি TTL বা ডেড-লেটার-কিউ (বারবার ব্যর্থ বার্তা আলাদা করে রাখা) কনফিগার করা হয় যাতে চিরকাল আটকে থাকা বার্তা সিস্টেমকে স্তব্ধ না করে দেয়।

অনুশীলন

  1. চিন্তা করুন: আপনার পরিচিত একটি অ্যাপে (যেমন Pathao, Facebook) ৩টি এমন কাজ চিহ্নিত করুন যেগুলো মেসেজ কিউ দিয়ে অ্যাসিনক্রোনাসভাবে করা যেতে পারে, এবং কেন সেগুলো তাৎক্ষণিক সিঙ্ক্রোনাস রেসপন্সের দরকার নেই তা ব্যাখ্যা করুন।

    উদাহরণ: (১) একটি অর্ডার কনফার্মেশন ইমেইল/SMS পাঠানো — ইউজারকে তাৎক্ষণিক দেখতে হবে না, কয়েক সেকেন্ড দেরি হলেও সমস্যা নেই। (২) আপলোড করা প্রোফাইল ছবি থেকে থাম্বনেইল জেনারেট করা — মূল আপলোড সফল হওয়ার পর ব্যাকগ্রাউন্ডে করা যায়। (৩) একটি রাইড শেষ হওয়ার পর অ্যানালিটিক্স/রিপোর্টিং ডেটাবেসে লগ করা — ইউজারের অভিজ্ঞতার সাথে সরাসরি সম্পর্কিত নয়, তাই সিঙ্ক্রোনাস পাথে রাখলে অযথা লেটেন্সি বাড়ে।

  2. কোড পরিবর্তন করুন: উপরের কোড সেলে produce() ফাংশনকে এমনভাবে বদলান যাতে কিউয়ের ক্যাপাসিটি সর্বোচ্চ ৩ হয় — ক্যাপাসিটি পূর্ণ থাকলে নতুন বার্তা প্রত্যাখ্যান (reject) করে একটি বার্তা প্রিন্ট করুক। তারপর ৪টি বার্তা পাঠিয়ে দেখুন কী হয়।

    সমাধানের কাঠামো: def produce(msg):
      if len(queue) >= 3:
        print(f"কিউ পূর্ণ — বার্তা প্রত্যাখ্যাত: {msg}")
        return
      queue.append(msg)
    । ৪টি বার্তা পাঠালে প্রথম ৩টি গৃহীত হবে, চতুর্থটি প্রত্যাখ্যাত হবে — এটি দেখায় বাস্তব মেসেজ ব্রোকারেও একটি সসীম ক্যাপাসিটি থাকে, এবং ক্যাপাসিটি পূর্ণ হলে সিস্টেমকে সিদ্ধান্ত নিতে হয় ব্লক করবে, প্রত্যাখ্যান করবে, নাকি পুরনো বার্তা ফেলে দেবে (backpressure হ্যান্ডলিং)।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
ক্যাশ ইনভ্যালিডেশন ও ইভিকশন পলিসি