পাঠ ৪৬ · ৫৮-এর মধ্যে · মডিউল ১০
Home / Courses / Full-Stack Web Frameworks / SSE ও পোলিং

Server-Sent Events ও পোলিং বিকল্প

Server-Sent Events & Polling Alternatives
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • Polling কীভাবে কাজ করে — একটি সত্যিকারের timer-loop সিমুলেশন
  • Server-Sent Events (SSE) কী — polling ও WebSocket-এর মাঝামাঝি একটি সমাধান
  • একই ইভেন্ট সিকোয়েন্সে polling ও push পদ্ধতির প্রকৃত নেটওয়ার্ক-কল সংখ্যা গণনা করে তুলনা করা
  • কখন polling-এর সরলতা যথেষ্ট, আর কখন push-ভিত্তিক পদ্ধতি সত্যিই প্রয়োজন

১ · Polling — ক্লায়েন্ট বারবার জিজ্ঞেস করে

PollingPollingক্লায়েন্ট নিয়মিত বিরতিতে বারবার সার্ভারকে "নতুন কিছু আছে?" জিজ্ঞেস করে — সার্ভার নিজে থেকে কখনো কিছু পাঠায় না। হলো real-time-এর সবচেয়ে সহজ সিমুলেশন — কোনো persistent connection বা special protocol লাগে না, ক্লায়েন্ট শুধু একটি টাইমারে নিয়মিত বিরতিতে সার্ভারকে জিজ্ঞেস করে "নতুন কিছু আছে?"। প্রতিটি জিজ্ঞাসাই একটি সম্পূর্ণ নেটওয়ার্ক রাউন্ড-ট্রিপ — উত্তর "না, কিছু নেই" হলেও।

২ · Server-Sent Events (SSE) — মাঝামাঝি একটি সমাধান

SSE সাধারণ HTTP-এর উপর একটি একমুখী (server → client) স্ট্রিম খোলে — ক্লায়েন্ট একবার কানেক্ট করার পর সার্ভার সেই একই কানেকশনে বারবার নতুন ইভেন্ট পাঠাতে থাকে, ক্লায়েন্টকে বারবার নতুন রিকোয়েস্ট পাঠাতে হয় না। এটি polling-এর চেয়ে দক্ষ (একটিমাত্র কানেকশন, বারবার রিকোয়েস্ট নয়) কিন্তু WebSocket-এর চেয়ে সহজ (শুধু সার্ভার → ক্লায়েন্ট দিকে কাজ করে, সাধারণ HTTP-এর উপর তৈরি, ক্লায়েন্ট থেকে সার্ভারে বার্তা পাঠাতে হলে এখনো আলাদা একটি রিকোয়েস্ট লাগে) — অনেকটা L45-এর Broker.publish()-এর মতোই আচরণ করে, শুধু দিক একমুখী। এই সাব্যাক্স-এ যেহেতু সত্যিকারের HTTP কানেকশন সম্ভব নয়, নিচের কোড সেলে আমরা মূল তুলনাটা — push বনাম pull — প্রকৃত কল-গণনা দিয়ে দেখাচ্ছি।

Polling
ক্লায়েন্ট বারবার নতুন রিকোয়েস্ট পাঠায়, প্রতিটি টিকে — সরল কিন্তু নষ্ট নেটওয়ার্ক কল বেশি।
SSE
একটি কানেকশনে সার্ভার একমুখী push করতে থাকে, HTTP-এর উপর — polling-এর চেয়ে দক্ষ, WebSocket-এর চেয়ে সহজ।
WebSocket
দ্বিমুখী, persistent কানেকশন — উভয় দিকেই যেকোনো সময় বার্তা যেতে পারে (L45)।

৩ · প্রকৃত কল-গণনা — Polling বনাম Push

নিচের কোড সেলে একই সিমুলেটেড ঘটনাপ্রবাহ — ১০টি টাইমার-টিক, যেখানে নতুন ডেটা শুধু টিক ৩ ও ৭-এ আসে — দুটো ভিন্ন পদ্ধতিতে চালানো হয়েছে। প্রথমে polling: প্রতিটি টিকে check_for_updates() কল করা হয় (মোট ১০টি কল), তারপর L45-এর Broker পুনরায় ব্যবহার করে push: broker.publish() শুধু তখনই কল হয় যখন সত্যিই নতুন ডেটা আছে (মোট ২টি কল)।

Python
# ---------- একই ইভেন্ট সিকোয়েন্স -- নতুন ডেটা শুধু টিক ৩ ও ৭-এ ----------
updates_source = {
    3: "নতুন অর্ডার: #1042",
    7: "নতুন অর্ডার: #1043",
}


# ---------- পদ্ধতি ১ -- POLLING ----------
def check_for_updates(tick):
    """প্রতিটি কলই একটি প্রকৃত নেটওয়ার্ক রাউন্ড-ট্রিপ -- উত্তর None হলেও কলটা হয়েই গেছে।"""
    return updates_source.get(tick)  # নতুন কিছু না থাকলে None


poll_call_count = 0
poll_received = []

print("=== POLLING ===")
for tick in range(1, 11):
    poll_call_count += 1  # প্রতিটি টিকেই একটি কল হয়, ফলাফল যা-ই হোক
    result = check_for_updates(tick)
    if result is not None:
        poll_received.append((tick, result))
        print(f"[Polling] টিক {tick}: নতুন ডেটা -> {result}")
    else:
        print(f"[Polling] টিক {tick}: কল করা হলো, কিছুই নতুন নেই")

print(f"\nমোট পোলিং নেটওয়ার্ক কল: {poll_call_count}")
print(f"এর মধ্যে আসলে দরকারি ছিল (নতুন ডেটা পাওয়া গেছে): {len(poll_received)}")


# ---------- পদ্ধতি ২ -- PUSH (L45-এর Broker পুনরায় ব্যবহার) ----------
class Broker:
    def __init__(self):
        self.subscribers = {}

    def subscribe(self, channel, callback):
        self.subscribers.setdefault(channel, []).append(callback)

    def publish(self, channel, message):
        for callback in self.subscribers.get(channel, []):
            callback(message)


push_call_count = 0


def client_handler(message):
    global push_call_count
    push_call_count += 1
    print(f"[Push] সার্ভার push করলো -> {message}")


broker = Broker()
broker.subscribe("orders", client_handler)

print("\n=== PUSH (Broker) ===")
for tick in range(1, 11):
    if tick in updates_source:
        broker.publish("orders", updates_source[tick])  # শুধু তখনই কল, যখন সত্যিই ডেটা আছে
    # অন্য টিকগুলোতে broker.publish() একবারও কল করা হয়নি -- কোনো নেটওয়ার্ক কলই নেই

print(f"\nমোট push নেটওয়ার্ক কল (client_handler আসলে কতবার কল হয়েছে): {push_call_count}")

print(f"\n--- তুলনা ---")
print(f"Polling: {poll_call_count} টি কল, এর মধ্যে {len(poll_received)} টি দরকারি ছিল")
print(f"Push:    {push_call_count} টি কল, সবগুলোই দরকারি ছিল")

    
poll_call_count ঠিক 10 হয়, কারণ for tick in range(1, 11) লুপে প্রতিটি টিকেই poll_call_count += 1 চলে — check_for_updates(tick)-এর রিটার্ন মান None হোক বা না হোক, গণনাটা লুপের প্রতিটি ধাপেই বাড়ে। অন্যদিকে push_call_count শুধু তখনই বাড়ে যখন client_handler সত্যিই কল হয় — আর সেটা কল হয় শুধু if tick in updates_source সত্য হলে, অর্থাৎ টিক ৩ ও ৭-এ — বাকি ৮টি টিকে broker.publish() একবারও কল করা হয়নি, তাই সেই টিকগুলোতে কোনো নেটওয়ার্ক কল-ই নেই। ফলে push_call_count ঠিক 2 হয়, ঠিক যতগুলো টিকে updates_source-এ সত্যিই ডেটা ছিল।
মূল কথা · Key takeaway

Polling সরল ও stateless — কোনো persistent connection দরকার নেই — কিন্তু প্রতিটি টাইমার-টিকে একটি নেটওয়ার্ক কল খরচ করে, উত্তর কিছু থাকুক বা না থাকুক। উপরের সিমুলেশনে ১০টি টিকে মাত্র ২বার আসল ডেটা থাকা সত্ত্বেও polling ১০টি কল করেছে, যেখানে push (L45-এর Broker, বা বাস্তবে SSE/WebSocket) ঠিক ততবারই কল হয়েছে যতবার সত্যিই কিছু ঘটেছে — মাত্র ২বার। যত বেশি সময় "কিছুই ঘটেনি" থাকে, polling তত বেশি অপচয় করে; সেই ক্ষেত্রে push-ভিত্তিক পদ্ধতি (SSE বা WebSocket) বেছে নেওয়াই যুক্তিসঙ্গত। তবে ইভেন্ট যদি প্রায় প্রতি টিকেই ঘটে, তাহলে polling-এর সরলতা push-এর অতিরিক্ত জটিলতার চেয়ে ভালো পছন্দ হতে পারে।

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

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

প্র ০১ কেন polling ১০টি কল করলো, অথচ পুরো সিকোয়েন্সে মাত্র ২বার নতুন ডেটা ছিল?

কারণ polling-এর কোড poll_call_count += 1-কে লুপের প্রতিটি পুনরাবৃত্তিতে চালায়, check_for_updates(tick)-এর ফলাফল দেখার আগেই — অর্থাৎ কলটা হয়েই যায়, তারপর ফলাফল None নাকি প্রকৃত ডেটা তা চেক করা হয়। Polling-এর সংজ্ঞাই এটা: "নিয়মিত বিরতিতে জিজ্ঞেস করা", উত্তর যা-ই হোক না কেন — তাই প্রতিটি টিকই একটি কল গণনা করে, নতুন ডেটা থাকা-না-থাকা নির্বিশেষে।

প্র ০২ push পদ্ধতিতে broker.publish() মোট কতবার কল হয়েছে, আর সেটা poll_call_count-এর সাথে কেন মেলে না?

broker.publish() কল হয়েছে ঠিক ২বার — if tick in updates_source শর্তটি শুধু টিক ৩ ও ৭-এই সত্য হয়। বাকি ৮টি টিকে সেই if-এর ভেতরের কোড ব্লকটাই এক্সিকিউট হয়নি, তাই broker.publish() কলই হয়নি। এটা poll_call_count-এর সাথে মেলে না, কারণ পোলিং লুপে কলটা শর্তহীনভাবে (unconditionally) প্রতি টিকেই ঘটে, আর push লুপে কলটা শর্তসাপেক্ষে (conditionally) শুধু ডেটা থাকলেই ঘটে — এটাই দুই পদ্ধতির মূল কাঠামোগত পার্থক্য।

প্র ০৩ SSE কীভাবে polling ও WebSocket-এর "মাঝামাঝি" একটি সমাধান?

SSE polling-এর চেয়ে দক্ষ, কারণ এটি একটিমাত্র দীর্ঘস্থায়ী HTTP কানেকশনে বারবার নতুন ইভেন্ট পাঠাতে পারে — পোলিং-এর মতো প্রতি টিকে নতুন কানেকশন/রিকোয়েস্ট লাগে না, তাই "কিছুই ঘটেনি"-তেও কোনো বাড়তি কল খরচ হয় না। আবার এটি WebSocket-এর চেয়ে সহজ, কারণ এটি একমুখী (শুধু server → client) এবং সাধারণ HTTP-এর উপর তৈরি — WebSocket-এর মতো সম্পূর্ণ দ্বিমুখী প্রোটোকল আপগ্রেড দরকার হয় না। যেসব ক্ষেত্রে শুধু সার্ভার থেকে ক্লায়েন্টে ডেটা যেতে হয় (যেমন লাইভ স্কোর আপডেট, নোটিফিকেশন), SSE প্রায়ই যথেষ্ট।

অনুশীলন

  1. চিন্তা করুন: যদি polling-এর ব্যবধান দ্বিগুণ ঘন করা হয় (১০ বারের বদলে ২০ বার জিজ্ঞেস করা, একই সময়ে একই ২টি আসল আপডেট থাকা অবস্থায়), poll_call_count ও push-এর push_call_count-এর মধ্যে ব্যবধান কেমন বদলাবে?

    poll_call_count দ্বিগুণ হয়ে 20-এ পৌঁছাবে (প্রতিটি টিকেই একটি কল, আর টিক সংখ্যা দ্বিগুণ), কিন্তু push_call_count একই থাকবে — 2, কারণ push শুধু সত্যিকারের ইভেন্ট ঘটলেই কল হয়, টিক কত ঘন সেটার সাথে এর কোনো সম্পর্ক নেই। অর্থাৎ polling-কে আরও "রেসপন্সিভ" করতে গেলে অপচয়ও বাড়ে, push পদ্ধতির খরচ একই থাকে — এটাই polling-এর মৌলিক দুর্বলতা।

  2. পরীক্ষা করুন: উপরের কোড সেলে updates_source dict-এ 5: "নতুন অর্ডার: #1044" যোগ করুন (এখন তিনটি টিকে ডেটা আছে — ৩, ৫, ৭), Run চেপে দেখুন len(poll_received) ও push_call_count দুটোই এখন কত হয়।

    দুটোই 3 হবে — poll_received-এ এখন তিনটি এন্ট্রি যোগ হবে (টিক ৩, ৫, ৭), আর push_call_count-ও তিনবার বাড়বে (একই তিনটি টিকে tick in updates_source সত্য হবে)। তবে poll_call_count এখনও 10-ই থাকবে, কারণ পোলিং লুপ সবসময় দশটি টিকই চালায়, ডেটা কতবার এলো তার উপর নির্ভর করে না — এটাই দেখায় polling-এর কল-সংখ্যা ইভেন্টের সংখ্যা থেকে সম্পূর্ণ স্বাধীন, push-এর কল-সংখ্যা ঠিক ইভেন্টের সংখ্যার সমান।

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

আগের পাঠ
WebSocket ও রিয়েল-টাইম কমিউনিকেশন