পাঠ ৪৬ · ৫১-এর মধ্যে · মডিউল ১১
Home / Courses / System Design / কেস স্টাডি: নিউজফিড

কেস স্টাডি: নিউজফিড/টাইমলাইন ডিজাইন করা

Case study: designing a social media feed
১১ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • নিউজফিড সিস্টেমের ফাংশনাল ও নন-ফাংশনাল রিকোয়ারমেন্ট, বিশেষ করে উচ্চ রিড:রাইট রেশিও
  • ফ্যান-আউট-অন-রাইট বনাম ফ্যান-আউট-অন-রিডের সঠিক ট্রেড-অফ
  • "সেলিব্রিটি সমস্যা" — কেন লক্ষ লক্ষ ফলোয়ারওয়ালা অ্যাকাউন্ট একক push মডেল ভেঙে দেয়
  • হাইব্রিড ফ্যান-আউট কীভাবে দুই পদ্ধতির সেরা অংশ একত্রে ব্যবহার করে

১ · রিকোয়ারমেন্ট

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

২ · ফ্যান-আউট-অন-রাইট (Push)

একজন ইউজার পোস্ট করলে, সিস্টেম সাথে সাথেই তার প্রতিটি ফলোয়ারের প্রি-কম্পিউটেড ফিডে (একটি ক্যাশ/দ্রুত স্টোর, L19) পোস্টটি লিখে দেয়। যখন ফলোয়ার নিজের ফিড দেখতে চায়, শুধু তার নিজের প্রি-কম্পিউটেড ফিড থেকে পড়ে — কোনো লাইভ গণনা ছাড়াই, তাই রিড অত্যন্ত দ্রুত। সমস্যা — একজন সেলিব্রিটির ১ কোটি ফলোয়ার থাকলে, একটি পোস্ট মানে ১ কোটি "রাইট" — একটি বিশাল, ব্যয়বহুল রাইট-স্টর্ম।

৩ · ফ্যান-আউট-অন-রিড (Pull)

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

৪ · হাইব্রিড পদ্ধতি — বাস্তব সিস্টেমের সমাধান

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

নতুন পোস্ট New Post সাধারণ ইউজার? push — fan-out-on-write সেলিব্রিটি? pull — fan-out-on-read ফলোয়ার ফিড ক্যাশ precomputed (L19) রিড-টাইম মার্জ merged feed response
সাধারণ ইউজারের পোস্ট আগে থেকেই ফলোয়ারের ফিড ক্যাশে বসে থাকে; সেলিব্রিটির পোস্ট রিড-টাইমে টেনে এনে মার্জ করা হয়।
Python
import collections

# কে কাকে ফলো করে (author -> follower তালিকা)
followers = {
    "alice": ["bob", "carol"],
    "dave":  ["carol"],
}

# ফলোয়ার-গ্রাফ থেকে "কে কাকে ফলো করে" ইনভার্ট করে বের করা (fan-out-on-read-এর জন্য দরকার)
following = collections.defaultdict(list)
for author, follower_list in followers.items():
    for follower in follower_list:
        following[follower].append(author)

posts = [
    ("alice", "Hello world"),
    ("dave",  "My first post"),
    ("alice", "Second post"),
]

# --- ফ্যান-আউট-অন-রাইট: পোস্ট হওয়ার সাথে সাথেই প্রতিটি ফলোয়ারের প্রি-কম্পিউটেড ফিডে যোগ ---
feeds_push = collections.defaultdict(list)

def fanout_on_write(author, text):
    for follower in followers.get(author, []):
        feeds_push[follower].append((author, text))

for author, text in posts:
    fanout_on_write(author, text)

# --- ফ্যান-আউট-অন-রিড: ফিড দেখার সময় ফলোকৃত সবার পোস্ট খুঁজে মার্জ করা ---
def fanout_on_read(user):
    return [(a, t) for a, t in posts if a in following.get(user, [])]

# --- যাচাই: দুই পদ্ধতিই একই ফিড কন্টেন্ট দেয় কি না ---
for user in ["bob", "carol"]:
    push_feed = feeds_push[user]
    pull_feed = fanout_on_read(user)
    match = "SAME" if push_feed == pull_feed else "DIFFERENT"
    print(f"{user}:")
    print(f"  push (fan-out-on-write): {push_feed}")
    print(f"  pull (fan-out-on-read):  {pull_feed}")
    print(f"  -> {match}\n")

    
লক্ষ্য করুন — bob এবং carol উভয়ের জন্যই push ও pull ফিড হুবহু একই কন্টেন্ট দেয় (SAME)। পার্থক্য শুধু কখন কাজটি হয় — push-এ পোস্ট হওয়ার সময়েই (আগে থেকে), pull-এ ফিড দেখার সময় (চাহিদামতো)। এটাই দেখায় কেন হাইব্রিড কৌশল সঠিক — একই ফলাফলের জন্য সঠিক ইউজারের ক্ষেত্রে সঠিক সময় বেছে নেওয়া, ফলাফল বদলানো নয়।

৫ · ট্রেড-অফ

রিড স্পিড বনাম রাইট খরচ
Push দ্রুত রিড দেয় কিন্তু ব্যয়বহুল রাইট (বিশেষত সেলিব্রিটির জন্য)। Pull সস্তা রাইট দেয় কিন্তু ধীর রিড।
সেলিব্রিটি থ্রেশহোল্ড
কোন সংখ্যক ফলোয়ারের পর একজন ইউজারকে "সেলিব্রিটি" (pull-এ পাঠানো) ধরা হবে, সেই থ্রেশহোল্ড ঠিক করাই একটি বাস্তব টিউনিং সিদ্ধান্ত।
Stale ফিড ক্যাশ
প্রি-কম্পিউটেড push ফিড L21-এর ক্যাশ ইনভ্যালিডেশন সমস্যার মুখোমুখি হয় — কেউ পোস্ট ডিলিট করলে তা সব ফলোয়ারের ফিড থেকেও সরাতে হয়।
মূল কথা · Key takeaway

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

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

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

প্র ০১ কেন একজন সেলিব্রিটির (১ কোটি ফলোয়ার) পোস্টের জন্য বিশুদ্ধ ফ্যান-আউট-অন-রাইট ব্যবহারিকভাবে ভেঙে পড়ে?

কারণ একটি পোস্ট মানেই ১ কোটি আলাদা রাইট অপারেশন (প্রতিটি ফলোয়ারের ফিডে একটি করে) — যা একটি বিশাল, আকস্মিক রাইট-স্টর্ম তৈরি করে যা সিস্টেমের রাইট-ক্যাপাসিটি (L02-এর ক্যাপাসিটি এস্টিমেশন) সহজেই ছাড়িয়ে যেতে পারে। এমনকি যদি সেই ১ কোটি ফলোয়ারের বেশিরভাগ কখনো লগইনই না করে, তবুও পুরো কাজটা করা হয়ে যায় — একটি বিশাল অপচয়।

প্র ০২ ফ্যান-আউট-অন-রিড কীভাবে সেলিব্রিটি সমস্যা সমাধান করে, এবং এর নিজস্ব দুর্বলতা কী?

ফ্যান-আউট-অন-রিডে পোস্ট করার সময় কিছুই প্রি-কম্পিউট হয় না — তাই ১ কোটি ফলোয়ার থাকলেও পোস্ট করা একটি সাধারণ, সস্তা অপারেশন। কিন্তু এর দুর্বলতা — প্রতিটি ফিড রিকোয়েস্টে, ইউজার যত জনকে ফলো করে তত জনের সাম্প্রতিক পোস্ট লাইভ খুঁজে মার্জ করতে হয় — যে ইউজার হাজার হাজার মানুষকে ফলো করে, তার প্রতিটি ফিড-লোড ধীর হয়ে যায়।

প্র ০৩ হাইব্রিড কৌশলে "সেলিব্রিটি থ্রেশহোল্ড" (কত ফলোয়ারের পর pull-এ পাঠানো হবে) খুব কম রাখলে, বা খুব বেশি রাখলে কী সমস্যা হতে পারে?

থ্রেশহোল্ড খুব কম রাখলে (যেমন ১০০ ফলোয়ারেই সেলিব্রিটি ধরা) — বেশি ইউজার pull-এ চলে যাবে, তাদের ফলোয়ারদের ফিড-লোড ধীর হবে, push-এর গতির সুবিধা হারিয়ে যাবে। থ্রেশহোল্ড খুব বেশি রাখলে (যেমন ১০ লক্ষ) — মাঝারি-বড় অ্যাকাউন্ট (যেমন ২ লক্ষ ফলোয়ার) এখনও push-এ থেকে যাবে এবং তাদের পোস্ট এখনও একটি বড় রাইট-স্টর্ম তৈরি করবে। সঠিক থ্রেশহোল্ড বাস্তব ট্রাফিক প্যাটার্ন পর্যবেক্ষণ করে টিউন করতে হয় — কোনো সর্বজনীন "সঠিক সংখ্যা" নেই।

অনুশীলন

  1. কোড বদলান: উপরের কোড সেলে "dave"-কে ১০ জন ফলোয়ার (bob সহ) দিন এবং একটি নতুন পোস্ট যোগ করুন। push ও pull ফিড আবার মিলিয়ে দেখুন এখনও SAME আসে কি না।

    followers["dave"]-এ আরও নাম যোগ করলে এবং posts-এ dave-এর নতুন পোস্ট যোগ করলে, fanout_on_write সেই নতুন ফলোয়ারদের feeds_push-এ পোস্টটি যোগ করবে, আর fanout_on_read স্বয়ংক্রিয়ভাবেই following ম্যাপিং থেকে সঠিক ফলোয়িকৃত-তালিকা বের করে মিলিয়ে দেবে — যেহেতু following ডিকশনারিটি followers থেকে প্রতিবার ইনভার্ট করে তৈরি হয়, উভয় পদ্ধতি এখনও সমান ফলাফল দেবে (SAME), শুধু কাজটি এখন বেশি ফলোয়ারের জন্য হচ্ছে।

  2. ডিজাইন করুন: একজন ইউজার একটি পুরনো পোস্ট এডিট করলে, push মডেলে (যেখানে পোস্ট আগে থেকেই সব ফলোয়ারের ফিডে কপি হয়ে আছে) এই পরিবর্তন কীভাবে সবার ফিডে প্রতিফলিত করবেন?

    দুটো সাধারণ পদ্ধতি — (১) প্রতিটি ফলোয়ারের ফিড-এন্ট্রিতে পূর্ণ কন্টেন্ট কপি না করে শুধু post_id রেফারেন্স রাখা, এবং আসল কন্টেন্ট একটি একক জায়গায় (মূল পোস্ট টেবিলে) রাখা — তাহলে এডিট একবারই আপডেট করলেই সব ফলোয়ারের ফিড স্বয়ংক্রিয়ভাবে আপডেটেড কন্টেন্ট দেখাবে (রেফারেন্স দিয়ে লুকআপ করার সময়)। অথবা (২) এডিটকে L21-এর cache invalidation সমস্যা হিসেবে ট্রিট করে, সব ফলোয়ারের ফিড-ক্যাশ থেকে সেই এন্ট্রি ইনভ্যালিডেট করে আবার fanout করা — যা ব্যয়বহুল, তাই পদ্ধতি (১) বাস্তবে বেশি প্রচলিত।

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

আগের পাঠ
কেস স্টাডি: চ্যাট/মেসেজিং সিস্টেম ডিজাইন করা