অধ্যায় ৫

Rate Limiting & Throttling

"Only allow X actions in Y time."

একটি RateLimiter অ্যাবস্ট্রাকশন — HTTP রিকোয়েস্ট, exception রিপোর্টিং, queue job আর আপনার নিজের লজিক, সবখানে।

~১৪ মিনিট১২টি স্লাইড

Rate Limiting কেন দরকার?

আপনার একটি লগইন পেজ আছে। কোনো হ্যাকার বট দিয়ে সেকেন্ডে ১০০০ বার পাসওয়ার্ড গেস করছে (Brute Force)। অথবা কেউ আপনার API বারবার কল করে সার্ভার বসিয়ে দিতে চাইছে।

প্রায় সব সিস্টেমের সীমা আছে — ডাটাবেস, API, অথেন্টিকেশন এন্ডপয়েন্ট, নোটিফিকেশন প্রোভাইডার এক সাথে নির্দিষ্ট পরিমাণের বেশি কাজ নিতে পারে না। Rate limiting ট্রাফিককে আকার দেয়, যাতে স্পাইক বা একজন আগ্রাসী ইউজারের কারণে আউটেজ না হয়।

লগইন / ভারী রুট

কড়া লিমিট — “প্রতি মিনিটে ৫টি লগইন চেষ্টা” brute force থামায়।

প্রতি ইউজার ন্যায্যতা

একজন ক্লায়েন্ট যেন API ক্যাপাসিটির ৯০% খেয়ে না ফেলে।

দুর্ঘটনাজনিত বন্যা

ভুল কনফিগার করা cron বা বাগি মোবাইল অ্যাপের হাজার রিকোয়েস্ট।

থার্ড-পার্টি API

তাদের কোটা পেরোলে আপনিই ব্লক হবেন।

লজিক সবসময় একই

  1. কে কাজটা করছে চিহ্নিত করো।
  2. কতবার করতে পারবে ঠিক করো।
  3. চেষ্টাগুলো গুনে রাখো।
  4. সীমা পেরোলে থামাও (যেমন 429 Too Many Requests)।

RateLimiter অ্যাবস্ট্রাকশন

RateLimiter::for('login', function (Request $request) {
    return Limit::perMinute(5)->by($request->ip());
});

এই তিন লাইনেই পুরো মেন্টাল মডেল:

কী লিমিট করছি

'login'

কতবার

perMinute(5)

কার উপর

by($request->ip())

বাকিটা লারাভেল করে — গণনা, সংরক্ষণ (সাধারণত Redis), এবং অনুমতি দেওয়া বা প্রত্যাখ্যান।

কোথায় লিখবো? (Laravel 11+)

app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('api', function (Request $request) {
        return $request->user()
            ? Limit::perMinute(100)->by($request->user()->id) // লগড-ইন: ১০০
            : Limit::perMinute(10)->by($request->ip());       // গেস্ট: ১০
    });
}

পুরোনো ভার্সনে (Laravel 8–10) এটা RouteServiceProvider-এ থাকতো।

Docs: Rate Limiting

HTTP Request Throttling

সরাসরি সংখ্যা দিয়ে

// প্রতি ১ মিনিটে সর্বোচ্চ ৬০টি রিকোয়েস্ট
Route::middleware(['throttle:60,1'])->group(function () {
    Route::get('/user', function () {
        // ...
    });
});

নামযুক্ত limiter দিয়ে

Route::post('/login', LoginController::class)->middleware('throttle:login');

ভেতরে সবকিছু একই RateLimiter ইঞ্জিন দিয়ে যায়, তাই পুরো অ্যাপে rate limiting সামঞ্জস্যপূর্ণ থাকে।

লিমিট পেরোলে কী হয়?

৬১তম রিকোয়েস্ট 429 Too Many Requests Retry-After হেডার

কাস্টম রেসপন্স চাইলে: Limit::perMinute(5)->by($ip)->response(fn () => response('আবার চেষ্টা করুন পরে', 429))

Docs: Attaching Rate Limiters

Exception Throttling

External API ডাউন হলে প্রতিটি রিকোয়েস্ট একই exception দেয় — লগ বা error-tracking সার্ভিস (Sentry, Flare) ভেসে যায়। লারাভেল রিপোর্ট করা exception throttle করতে দেয়:

bootstrap/app.php
use Illuminate\Support\Lottery;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000); // প্রতি ১০০০টিতে ১টি রিপোর্ট
    });
})

Lottery

র‍্যান্ডম স্যাম্পলিং — মোট সংখ্যার একটা অংশ রিপোর্ট।

Limit

নির্দিষ্ট হার — যেমন প্রতি মিনিটে সর্বোচ্চ ২০০টি।

শুধু নির্দিষ্ট noisy exception throttle করা

use Illuminate\Cache\RateLimiting\Limit;

$exceptions->throttle(function (Throwable $e) {
    if ($e instanceof ExternalApiException) {
        return Limit::perMinute(200);
    }
});

ক্রিটিক্যাল এরর সব রিপোর্ট হয়, পুনরাবৃত্ত এরর নিয়ন্ত্রণে থাকে।

Exception throttling মনিটরিং-এর বিকল্প নয়। এটা ফেইলিওর স্পাইকের সময় রিপোর্টিং সিস্টেম সুস্থ রাখে।

Docs: Throttling Reported Exceptions

Queue Job Throttling

দুটি উপায়, দুটি আলাদা সমস্যার জন্য।

১. RateLimited — throughput সীমিত করা

use Illuminate\Queue\Middleware\RateLimited;

public function middleware(): array
{
    return [new RateLimited('external-api')];
}

Limiter ডিফাইন করুন RateLimiter::for('external-api', ...) দিয়ে। লিমিট পেরোলে লারাভেল job-কে অটোমেটিক্যালি queue-তে ফেরত পাঠায় (release) — ম্যানুয়ালি delay সামলাতে হয় না।

Docs: Job Rate Limiting

২. ThrottlesExceptions — বারবার ফেইল করলে থামা

use Illuminate\Queue\Middleware\ThrottlesExceptions;

public function middleware(): array
{
    return [new ThrottlesExceptions(10, 300)];
}

Job ১০টির বেশি exception দিলে লারাভেল ৩০০ সেকেন্ড throttle করে, তারপর আবার চেষ্টা। অস্থির dependency-কে রিকভারির সময় দেয়।

Docs: Throttling Exceptions
Middlewareকখন
RateLimitedরিসোর্সের পরিচিত ব্যবহার-সীমা আছে
ThrottlesExceptionsরিসোর্স অস্থির বা মাঝে মাঝে ফেইল করে
দুটোই একসাথেoverload ও failure loop দুটো থেকেই রক্ষা

Throttling-এ Redis কেন?

প্রতিটি লিমিট চেক (counter বাড়ানো, timestamp দেখা, lock নেওয়া) একটি atomic অপারেশন হতে হয়। Redis এটা মেমরিতে করে, ডিস্কে নয়।

Atomic Increment

INCR, INCRBY — race condition ছাড়াই গণনা।

Key Expiry

উইন্ডো শেষ হলে লিমিট নিজেই রিসেট।

ক্লাস্টারে সামঞ্জস্য

একাধিক অ্যাপ সার্ভারে একই কাউন্টার।

প্রথাগত ডাটাবেস বা file store স্কেলে এগুলো নির্ভরযোগ্যভাবে পারে না।

বোনাস: Redis-নির্দিষ্ট টুল

// Queue job-এর জন্য Redis-অপ্টিমাইজড middleware
use Illuminate\Queue\Middleware\RateLimitedWithRedis;

return [new RateLimitedWithRedis('external-api')];

// অথবা সরাসরি Redis throttle — ৬০ সেকেন্ডে সর্বোচ্চ ১০টি
Redis::throttle('key')->allow(10)->every(60)->then(function () {
    // Job logic...
}, function () {
    return $this->release(10); // lock পাওয়া যায়নি
});

সঠিক Scope বাছাই

ভুল scope হলে ভুল মানুষ ব্লক হবে, অথবা কিছুই রক্ষা হবে না।

Per-User — ন্যায্যতার জন্য

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

প্রত্যেকের নিজের কোটা — একজন আগ্রাসী ক্লায়েন্ট সবাইকে ধীর করতে পারে না। পাবলিক API, লগইন, অথেন্টিকেটেড অ্যাকশনের জন্য।

Global — সিস্টেম-ব্যাপী ক্যাপাসিটি

RateLimiter::for('external-api', function () {
    return Limit::perMinute(500);
});

সবার মধ্যে শেয়ার। থার্ড-পার্টি API-র মোট কোটা বা ব্যয়বহুল background task-এর জন্য।

Hybrid — দুটোই

RateLimiter::for('uploads', function (Request $request) {
    return [
        Limit::perMinute(60)->by($request->user()->id), // প্রতি ইউজার
        Limit::perMinute(500),                          // সবাই মিলে
    ];
});

ইউজাররা এককভাবেও না, সম্মিলিতভাবেও না — সিস্টেম ওভারলোড করতে পারবে না।

HTTP-এর বাইরে Rate Limit

RateLimiter::attempt() সাধারণ PHP-তে কাজ করে — service, domain logic, console command, scheduled task, যেকোনো জায়গায়:

use Illuminate\Support\Facades\RateLimiter;

RateLimiter::attempt(
    'import-check',
    $perMinute = 5,
    function () {
        // শুধু লিমিটের মধ্যে থাকলে চলবে
        dispatch(new CheckExternalImport);
    }
);

import-check key গত এক মিনিটে ৫ বারের কম ব্যবহার হলে callback চলবে; না হলে স্কিপ — অপ্রয়োজনীয় কাজ এড়ানো।

ম্যানুয়াল নিয়ন্ত্রণ

// উদাহরণ: অ্যাডমিনকে এরর ইমেইল ৫ মিনিটে একবারের বেশি নয়
if (RateLimiter::tooManyAttempts('send-error-email', 1)) {
    $seconds = RateLimiter::availableIn('send-error-email');
    return; // এই সময়ের মধ্যে ইমেইল আগেই গেছে
}

RateLimiter::hit('send-error-email', 300); // ৩০০ সেকেন্ডের উইন্ডো
// অ্যাডমিনকে এরর রিপোর্ট পাঠান...

RateLimiter::clear('send-error-email'); // দরকার হলে রিসেট

মূল কথা

Rate limiting HTTP-তে বাঁধা নয় — এটা একটি general-purpose টুল।

যা মাথায় রাখবেন

সঠিক পরিচয় বাছুন

IP, ইউজার, নাকি গ্লোবাল? ভুল scope বৈধ ইউজার ব্লক করে বা আক্রমণকারীকে ঢুকতে দেয়। Limiter লেখার আগেই ভাবুন।

উইন্ডো টিউন করুন

১ মিনিট আর ১০ সেকেন্ডের উইন্ডো খুব আলাদা আচরণ করে। ছোট উইন্ডো মসৃণ; বড় উইন্ডো স্পাইক অনুমতি দেয়।

এটা সিকিউরিটি গ্যারান্টি নয়

অপব্যবহারের খরচ বাড়ায়, কিন্তু দৃঢ় আক্রমণকারী সাধারণ লিমিট এড়াতে পারে। এটা বৃহত্তর প্রতিরক্ষার একটি স্তর। Distributed counter মিলিসেকেন্ড পর্যন্ত নিখুঁত নয় — তবে বাস্তবে যথেষ্ট।

“Blocked” মানে কী?

429 দিয়ে প্রত্যাখ্যান? দেরি করানো? নীরবে বাদ? কে কল করছে আর কী আশা করছে তার উপর নির্ভর করে।

পুরো লারাভেলে Rate Limiting

কোথায়API
Limiter ডিফাইনRateLimiter::for('name', fn)
HTTP Routethrottle:60,1 / throttle:login
Exception রিপোর্টিং$exceptions->throttle(...)
Queue JobRateLimited, ThrottlesExceptions
যেকোনো কোডRateLimiter::attempt(), hit(), tooManyAttempts()

মূল কথা

প্রতিটির কনফিগারেশন আলাদা, কিন্তু সবাই একই RateLimiter অ্যাবস্ট্রাকশন দিয়ে চলে: RateLimiter::for(...) দিয়ে ডিফাইন করুন, তারপর route, job বা নিজের কোডে প্রয়োগ করুন। মেন্টাল মডেল প্রেক্ষাপট নির্বিশেষে একই।

সারসংক্ষেপ ও যাচাই

  • প্রতিটি limiter তিনটি প্রশ্নের উত্তর: কী, কতবার, কার উপর।
  • Laravel 11+-এ limiter AppServiceProvider::boot()-এ ডিফাইন করুন।
  • Job-এ: পরিচিত কোটা → RateLimited; অস্থির সার্ভিস → ThrottlesExceptions।
  • Atomic ও দ্রুত হওয়ায় throttling-এর জন্য Redis সেরা।
  • Per-user, global বা hybrid — scope ভেবেচিন্তে বাছুন।

নিজেকে যাচাই করুন

throttle:60,1-এর দুটি সংখ্যার মানে কী?

সর্বোচ্চ ৬০টি রিকোয়েস্ট, প্রতি ১ মিনিটে।

থার্ড-পার্টি API-র মোট কোটা ৫০০/মিনিট। কোন scope?

Global — Limit::perMinute(500) কোনো by() ছাড়া, কারণ কোটা সবার মধ্যে শেয়ার।

অফিসের সব কর্মী একই IP থেকে আসে। IP-ভিত্তিক লিমিটে সমস্যা কী?

সবাই একই কোটা শেয়ার করবে, ফলে বৈধ ইউজাররা ব্লক হবে। অথেন্টিকেটেড হলে user()->id দিয়ে লিমিট করুন।