পাঠ ৩৫ · ৫১-এর মধ্যে · মডিউল ৯
Home / Courses / System Design / রেট লিমিটিং অ্যালগরিদম

রেট লিমিটিং অ্যালগরিদম

Rate limiting algorithms
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • রেট লিমিটিং কেন দরকার এবং চারটি প্রধান অ্যালগরিদমের কাজের পদ্ধতি
  • Token Bucket বনাম Leaky Bucket — বার্স্ট অনুমোদন বনাম আউটপুট স্মুথিং-এর পার্থক্য
  • Fixed Window Counter-এর বাউন্ডারি সমস্যা এবং Sliding Window কীভাবে তা ঠিক করে
  • Python-এ একটি সম্পূর্ণ Token Bucket ক্লাস বানানো — রিফিল, বার্স্ট ও ডিনায়াল সিমুলেট করে

১ · সমস্যা — কেন প্রতিটি প্রোডাকশন সিস্টেমে রেট লিমিটিং দরকার

একটি পাবলিক API যদি ক্লায়েন্টদের রিকোয়েস্ট হারে কোনো সীমা না রাখে, তাহলে একটি একক buggy স্ক্রিপ্ট বা ম্যালিশিয়াস ক্লায়েন্ট প্রতি সেকেন্ডে হাজার হাজার রিকোয়েস্ট পাঠিয়ে পুরো সার্ভিসকে অন্য সব ইউজারের জন্য ধীর বা অচল করে দিতে পারে। রেট লিমিটিংRate Limitingএকটি নির্দিষ্ট সময়সীমার মধ্যে একটি ক্লায়েন্ট বা ইউজার সর্বোচ্চ কতগুলো রিকোয়েস্ট করতে পারবে তা সীমিত করার কৌশল — সীমা অতিক্রম করলে অতিরিক্ত রিকোয়েস্ট প্রত্যাখ্যান বা বিলম্বিত করা হয়। এই সমস্যার সমাধান দেয় — প্রতিটি ক্লায়েন্টের জন্য একটি ন্যায্য সীমা বেঁধে দিয়ে, বাকি সবার জন্য সিস্টেমটিকে সুস্থিত রাখা।

২ · Token Bucket — বার্স্ট অনুমোদনকারী সবচেয়ে জনপ্রিয় অ্যালগরিদম

Token Bucket-এ একটি বাকেট থাকে যাতে সর্বোচ্চ capacity-টি টোকেন ধরে, এবং একটি ফিক্সড হারে (refill_rate, প্রতি সেকেন্ডে) নতুন টোকেন যোগ হতে থাকে (ধারণক্ষমতা পর্যন্ত)। প্রতিটি রিকোয়েস্ট ১টি টোকেন খরচ করে; বাকেট খালি হলে রিকোয়েস্ট প্রত্যাখ্যাত বা বিলম্বিত হয়।

কেন এটি বার্স্ট অনুমোদন করে

যদি বাকেট বেশ কিছুক্ষণ ব্যবহার না হয়ে পূর্ণ থাকে (যেমন ৫টি টোকেন), তাহলে হঠাৎ ৫টি রিকোয়েস্ট একসাথে এলেও সবগুলো অনুমোদিত হবে — একটি ছোট বার্স্ট সহ্য করা হলো। কিন্তু দীর্ঘমেয়াদে গড় রেট refill_rate-এর বেশি হতে পারবে না, কারণ টোকেন ফুরিয়ে গেলে নতুন টোকেন আসার গতির উপরই পরবর্তী রিকোয়েস্টগুলো নির্ভরশীল।

৩ · Leaky Bucket — আউটপুট স্মুথিং

Leaky Bucket-এ রিকোয়েস্ট একটি কিউতে জমা হয় এবং সবসময় একটি স্থির হারে "লিক" করে বের হয় (প্রসেস হয়) — ইনপুটে যতই বার্স্ট আসুক না কেন। এটি Token Bucket-এর ঠিক বিপরীত দর্শন —

Token Bucket
বার্স্ট অনুমোদন করে — আউটপুট রেট মুহূর্তে মুহূর্তে বদলাতে পারে, তবে গড়ে সীমার মধ্যে থাকে।
Leaky Bucket
আউটপুট রেট সবসময় স্থির — ইনপুট বার্স্টি হলেও আউটপুট মসৃণ, ডাউনস্ট্রিম সিস্টেমের জন্য প্রেডিক্টেবল লোড তৈরি করে।

৪ · Fixed Window Counter ও তার বাউন্ডারি সমস্যা

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

Sliding Window (লগ বা কাউন্টার-ভিত্তিক) এই সমস্যা সমাধান করে ফিক্সড বাউন্ডারির বদলে একটি চলমান সময়-উইন্ডো (যেমন "গত ৬০ সেকেন্ড") ট্র্যাক করে — অতিরিক্ত মেমরি/গণনার খরচে, তবে বাউন্ডারি-বার্স্ট সমস্যা থাকে না।

রিকোয়েস্ট request টোকেন বাকেট tokens ≥ 1? ALLOWED টোকেন -১ DENIED প্রত্যাখ্যাত
প্রতিটি রিকোয়েস্টে বাকেট প্রথমে সময়ের হিসেবে রিফিল হয়, তারপর টোকেন থাকলে অনুমোদন দিয়ে ১টি টোকেন খরচ করে, না থাকলে প্রত্যাখ্যান করে।

৫ · Python-এ একটি সম্পূর্ণ Token Bucket

নিচের কোডে একটি TokenBucket ক্লাস আছে যার allow_request(current_time) মেথড প্রথমে অতিবাহিত সময় অনুযায়ী টোকেন রিফিল করে (ধারণক্ষমতা পর্যন্ত), তারপর টোকেন থাকলে একটি খরচ করে True রিটার্ন করে, না থাকলে False। বাস্তব সময়ের বদলে এক্সপ্লিসিট টাইমস্ট্যাম্প ব্যবহার করা হয়েছে যাতে ফলাফল সম্পূর্ণ ডিটারমিনিস্টিক হয়।

Python
class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity          # বাকেটে সর্বোচ্চ কতগুলো টোকেন থাকতে পারে
        self.refill_rate = refill_rate    # প্রতি সেকেন্ডে কতগুলো টোকেন যোগ হয়
        self.tokens = capacity            # শুরুতে বাকেট পূর্ণ
        self.last_check = 0.0             # শেষবার রিফিল করা হয়েছিল কখন

    def allow_request(self, current_time):
        elapsed = current_time - self.last_check
        refill = elapsed * self.refill_rate
        self.tokens = min(self.capacity, self.tokens + refill)
        self.last_check = current_time

        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

bucket = TokenBucket(capacity=5, refill_rate=1)   # ৫ টোকেন ধারণক্ষমতা, প্রতি সেকেন্ডে ১টি রিফিল

# পর্যায় ১: t=0 সময়ে ৭টি রিকোয়েস্ট একসাথে (burst) — বাকেটে মাত্র ৫টি টোকেন আছে
print("পর্যায় ১ — t=0 তে বার্স্ট (৭টি রিকোয়েস্ট):")
for i in range(7):
    allowed = bucket.allow_request(current_time=0)
    print(f"  রিকোয়েস্ট {i+1}: {'ALLOWED' if allowed else 'DENIED'} (tokens বাকি ≈ {bucket.tokens:.1f})")

# পর্যায় ২: ৩ সেকেন্ড অপেক্ষা (৩টি টোকেন রিফিল হবে)
print("\nপর্যায় ২ — t=3 (৩ সেকেন্ড অপেক্ষার পর, ৩টি রিকোয়েস্ট):")
for i in range(3):
    allowed = bucket.allow_request(current_time=3)
    print(f"  রিকোয়েস্ট {i+1}: {'ALLOWED' if allowed else 'DENIED'} (tokens বাকি ≈ {bucket.tokens:.1f})")

# পর্যায় ৩: t=3 তেই আরেকটি বার্স্ট — টোকেন প্রায় শেষ হয়ে যাবে
print("\nপর্যায় ৩ — t=3 তেই আরও ৩টি রিকোয়েস্ট (একই মুহূর্তে, নতুন রিফিল নেই):")
for i in range(3):
    allowed = bucket.allow_request(current_time=3)
    print(f"  রিকোয়েস্ট {i+1}: {'ALLOWED' if allowed else 'DENIED'} (tokens বাকি ≈ {bucket.tokens:.1f})")

    
লক্ষ্য করুন — পর্যায় ১-এ ৭টির মধ্যে প্রথম ৫টি ALLOWED হয় (বাকেটের পূর্ণ ধারণক্ষমতা), বাকি ২টি DENIED। পর্যায় ২-এ ৩ সেকেন্ড অপেক্ষার পর ৩টি নতুন টোকেন রিফিল হয়, তাই ৩টি রিকোয়েস্টই ALLOWED হয়। পর্যায় ৩-এ একই মুহূর্তে (t=3) আরও রিকোয়েস্ট এলে — যেহেতু কোনো নতুন সময় অতিবাহিত হয়নি তাই নতুন রিফিল নেই — বাকেট খালি থাকায় সবগুলো DENIED হয়। এটাই Token Bucket-এর মূল আচরণ: বার্স্ট অনুমোদিত, কিন্তু দীর্ঘমেয়াদী গড় রেট রিফিল রেট দ্বারা সীমাবদ্ধ।
মূল কথা · Key takeaway

চারটি অ্যালগরিদমের কোনোটিই সার্বজনীনভাবে "সেরা" নয় — Token Bucket বার্স্ট-বান্ধব API-এর জন্য সবচেয়ে জনপ্রিয় (AWS, Stripe এর মতো অনেক পাবলিক API এটি ব্যবহার করে), Leaky Bucket তখন উপযুক্ত যখন ডাউনস্ট্রিম সিস্টেমের জন্য একদম স্থির লোড দরকার, এবং Sliding Window তখন দরকার যখন Fixed Window-এর বাউন্ডারি-বার্স্ট গ্রহণযোগ্য নয়। L44-এ আমরা দেখব কীভাবে এই একই অ্যালগরিদমগুলো একটি মাল্টি-সার্ভার রেট লিমিটারে প্রয়োগ করতে হয়, যেখানে একটি শেয়ার্ড স্টোর প্রয়োজন হয়।

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

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

প্র ০১ Fixed Window Counter-এর বাউন্ডারি সমস্যাটি একটি নির্দিষ্ট সংখ্যার উদাহরণ দিয়ে ব্যাখ্যা করুন — ধরুন সীমা প্রতি মিনিটে ১০০ রিকোয়েস্ট।

ধরুন মিনিট ১-এর শেষ ১ সেকেন্ডে (00:59) ঠিক ১০০টি রিকোয়েস্ট আসে (মিনিট ১-এর সীমার মধ্যেই, বৈধ), এবং মিনিট ২-এর প্রথম ১ সেকেন্ডে (01:00) আবার ঠিক ১০০টি রিকোয়েস্ট আসে (মিনিট ২-এর নতুন কাউন্টারে, এটিও বৈধ)। প্রতিটি উইন্ডো নিজে নিজের ১০০-সীমা মেনে চলেছে, কিন্তু বাস্তবে মাত্র ২ সেকেন্ডের একটি সময়ব্যাপ্তিতে (00:59-01:01) সিস্টেমটি ২০০টি রিকোয়েস্ট প্রসেস করেছে — অর্থাৎ কার্যকর রেট ইচ্ছাকৃত সীমার প্রায় ২ গুণ, শুধু উইন্ডোর সংযোগস্থলে পড়ার কারণে।

প্র ০২ একটি ভিডিও-স্ট্রিমিং সার্ভিস কেন Token Bucket-এর বদলে Leaky Bucket পছন্দ করতে পারে ডাউনস্ট্রিম ট্রান্সকোডিং সার্ভিসে রিকোয়েস্ট পাঠানোর জন্য?

ট্রান্সকোডিং-এর মতো ভারী, CPU-ইনটেনসিভ কাজে ডাউনস্ট্রিম সার্ভিসকে একটি অনুমানযোগ্য (predictable), স্থির হারে কাজ পাঠানো জরুরি — একটি হঠাৎ বার্স্ট (Token Bucket যা অনুমোদন করে) ট্রান্সকোডিং ক্লাস্টারকে মুহূর্তের জন্য ওভারলোড করে দিতে পারে। Leaky Bucket ইনপুট বার্স্টি হলেও আউটপুটকে সবসময় একটি নির্দিষ্ট, পরিচালনাযোগ্য হারে "লিক" করায় — ফলে ডাউনস্ট্রিম ক্যাপাসিটি প্ল্যানিং (L02) অনেক সহজ ও নির্ভরযোগ্য হয়।

প্র ০৩ রেট লিমিট অতিক্রান্ত হলে সার্ভার সাধারণত কোন HTTP স্ট্যাটাস কোড রিটার্ন করে (L06 দেখুন), এবং ক্লায়েন্টকে আর কী তথ্য জানানো উচিত?

স্ট্যান্ডার্ড স্ট্যাটাস কোড হলো 429 Too Many Requests। ভালো API ডিজাইন সাধারণত রেসপন্স হেডারে অতিরিক্ত তথ্য দেয় — যেমন Retry-After (কত সেকেন্ড পরে আবার চেষ্টা করা উচিত) এবং X-RateLimit-Remaining (বর্তমান উইন্ডোতে আর কতগুলো রিকোয়েস্ট বাকি আছে) — যাতে ক্লায়েন্ট বুদ্ধিমানের মতো রিট্রাই করতে পারে (এবং L27-এর এক্সপোনেনশিয়াল ব্যাকঅফ কৌশল প্রয়োগ করতে পারে), শুধু অন্ধভাবে আবার চেষ্টা করে সমস্যা আরও না বাড়িয়ে।

অনুশীলন

  1. পরিবর্তন করুন: উপরের কোড সেলে capacity ১০ ও refill_rate ২ করুন, তারপর t=0 তে ১২টি রিকোয়েস্টের বার্স্ট পাঠিয়ে দেখুন কতগুলো ALLOWED হয়।

    capacity=10 মানে বাকেট শুরুতে ১০টি টোকেন নিয়ে পূর্ণ থাকে। t=0 তে ১২টি রিকোয়েস্ট এলে প্রথম ১০টি ALLOWED হবে (প্রতিটি ১টি করে টোকেন খরচ করে বাকেট খালি করে দেবে), বাকি ২টি DENIED হবে — refill_rate এখানে প্রভাব ফেলে না কারণ t=0 তেই সব রিকোয়েস্ট আসছে, কোনো সময় অতিবাহিত হয়নি বলে কোনো রিফিলও হয়নি।

  2. ডিজাইন করুন: একটি লগইন এন্ডপয়েন্টের জন্য (ব্রুট-ফোর্স পাসওয়ার্ড অ্যাটাক ঠেকাতে) কোন রেট লিমিটিং অ্যালগরিদম বেছে নেবেন এবং কী কী প্যারামিটার (সীমা, উইন্ডো) ব্যবহার করবেন তা যুক্তিসহ লিখুন।

    একটি সাধারণ পছন্দ — Sliding Window বা Fixed Window Counter ব্যবহার করে প্রতি IP/ইউজারনেমের জন্য প্রতি ১৫ মিনিটে সর্বোচ্চ ৫টি ব্যর্থ লগইন চেষ্টা অনুমোদন করা, তারপর একটি সাময়িক লকআউট। Token Bucket-এর মতো বার্স্ট-বান্ধব অ্যালগরিদম এখানে কম উপযুক্ত, কারণ লগইনের ক্ষেত্রে আমরা ঠিক উল্টোটা চাই — কোনো বার্স্ট অনুমোদন না করাই নিরাপত্তার জন্য ভালো, যেহেতু একটি বার্স্ট মানেই সম্ভাব্য ব্রুট-ফোর্স অ্যাটাকের লক্ষণ।

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

আগের পাঠ
CRDT পরিচিতি