অধ্যায় ৯

Deferring Work

"Respond first, handle the rest afterward."

ইউজারকে দ্রুত রেসপন্স দিয়ে অপ্রয়োজনীয় কাজ পরে করা — queue ছাড়াই হালকা উপায়ে।

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

কাজ পরে কেন করবো?

ইউজার অর্ডার দিলো। অ্যাপকে কনফার্মেশন ইমেইল পাঠাতে হবে, অ্যানালিটিক্স আপডেট করতে হবে, ট্রানজেকশন লগ করতে হবে। এর কোনোটাই ইউজারের “Order confirmed” দেখার আগে হওয়ার দরকার নেই। কিন্তু সব synchronously করলে রেসপন্স ২০০ মিলিসেকেন্ডের বদলে ২ সেকেন্ড নেয়।

সব আগে (২০০০ms)

অর্ডারইমেইলঅ্যানালিটিক্সলগরেসপন্স

Defer করে (২০০ms)

অর্ডাররেসপন্সইমেইল · অ্যানালিটিক্স · লগ

Deferring মানে

‘Critical path থেকে execution সরিয়ে দেওয়া’। আগে রেসপন্স, বাকিটা পরে। অথবা কাজটা আগে ডিফাইন করা, কিন্তু আসলে দরকার হলেই চালানো।

এর সবচেয়ে সম্পূর্ণ রূপ queue — কিন্তু লারাভেল আরও হালকা উপায় দেয়, যখন কাজটা “শীঘ্রই” হলেই চলে, আর queue-এর retry, persistence বা worker ইনফ্রাস্ট্রাকচার লাগে না।

Terminable Middleware

সবচেয়ে সরাসরি deferring ঘটে HTTP রেসপন্স পাঠানোর পরে। PHP প্রসেস তখনো জীবিত, কিন্তু ইউজার আর অপেক্ষা করছে না।

class LogRequest
{
    public function handle(Request $request, Closure $next)
    {
        return $next($request); // রিকোয়েস্ট চলাকালীন
    }

    public function terminate(Request $request, Response $response): void
    {
        // রেসপন্স ব্রাউজারে যাওয়ার পর
        Log::info('Request processed: ' . $request->url());
    }
}
মেথডকখন চলে
handle()রিকোয়েস্ট চলাকালীন
terminate()রেসপন্স পাঠানোর পর

লগিং, মেট্রিক্স সংগ্রহ বা এমন যেকোনো ক্লিনআপ, যা ইউজার যা দেখে তাকে প্রভাবিত করে না।

এটা ঠিকভাবে কাজ করতে সার্ভার FastCGI (PHP-FPM) হতে হবে, যাতে রেসপন্স আগে ফ্লাশ হয়।

Docs: Terminable Middleware

defer() Helper

রেসপন্সের পরে কাজ ঠেলে দেওয়ার সবচেয়ে সহজ উপায় (Laravel 11+):

use function Illuminate\Support\defer;

$order = Order::create([...]);

defer(fn () => Mail::to($order->user)->send(new Receipt($order)));

return response()->json(['ok' => true]);

রেসপন্স সাথে সাথে চলে যায়। ইমেইল পরে যায় — একই PHP প্রসেসে।

বাস্তব উদাহরণ: ভিউ কাউন্ট

public function show(Post $post)
{
    defer(fn () => $post->increment('views')); // পরে চলবে

    return view('post.show', ['post' => $post]); // ইউজার সাথে সাথে পেজ পাবে
}
ফিচারকোড
রেসপন্স এরর হলেও চালাওdefer(fn () => ...)->always();
নাম দেওয়াdefer(fn () => ..., 'reportMetrics');
বাতিল করাDefer::forget('reportMetrics');
টেস্টে সাথে সাথে চালানো$this->withoutDefer();
Docs: Deferred Functions

রেসপন্সের পর Job Dispatch

SendEmail::dispatchAfterResponse($user);

// অথবা closure
dispatch(function () {
    // কিছু করো
})->afterResponse();

এটা আসল background job নয়! রেসপন্স পাঠানোর পর চলে, কিন্তু একই PHP প্রসেসে। কোনো retry নেই, persistence নেই, queue worker নেই। প্রসেস মারা গেলে কাজটাও হারিয়ে যায়।

ঐতিহাসিকভাবে লারাভেল এটা dispatchAfterResponse() দিয়ে করতো; নতুন defer() একই কাজে বেশি স্পষ্ট ও নমনীয়। dispatchAfterResponse() এখন আর ডকুমেন্টেড নয়, তবে Dispatchable trait-এর মাধ্যমে এখনো কাজ করে।

Event ও HTTP Batch Defer

Event defer করা

OrderPlaced::dispatchAfterResponse($order);

Event-এর listener ভারী হলে আর রিকোয়েস্টের সময় ফলাফল না লাগলে কাজে আসে। Job-এর মতোই এটা Dispatchable trait ব্যবহার করে — ডকুমেন্টেড নয়, কিন্তু কাজ করে।

HTTP Client Batch defer করা

একাধিক outbound HTTP রিকোয়েস্ট ব্যাচ করে রেসপন্সের পরে চালানো:

use Illuminate\Http\Client\Batch;
use Illuminate\Support\Facades\Http;

$responses = Http::batch(fn (Batch $batch) => [
    $batch->get('http://localhost/first'),
    $batch->get('http://localhost/second'),
    $batch->get('http://localhost/third'),
])->then(function (Batch $batch, array $results) {
    // সব রিকোয়েস্ট সফল...
})->defer();

defer() কল লারাভেলকে বলে — রিকোয়েস্টের সময় নয়, রেসপন্স পাঠানোর পরে ব্যাচটা চালাও।

Docs: Deferring Batches

Deferred Service Providers

লারাভেল একই আইডিয়া ফ্রেমওয়ার্ক লেভেলেও প্রয়োগ করে। কিছু service provider deferred — শুধু তখনই লোড হয় যখন কেউ সত্যিই তাদের binding রিজলভ করে:

use Illuminate\Contracts\Support\DeferrableProvider;

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register(): void
    {
        $this->app->singleton(Connection::class, function (Application $app) {
            return new Connection($app['config']['riak']);
        });
    }

    public function provides(): array
    {
        return [Connection::class]; // কোন binding দিলে লোড হবে
    }
}
অ্যাপ boot Riak provider স্কিপ app(Connection::class) এখন লোড

Boot-এ সব provider লোড না করে, লারাভেল deferred provider স্কিপ করে যতক্ষণ না তাদের সার্ভিস চাওয়া হয়। Boot টাইম কমে, যে কোড একটি রিকোয়েস্টে কখনো লাগবে না তা লোড হয় না।

Docs: Deferred Providers

Defer বনাম Queue

দুটোই কাজ পরে করে। পার্থক্য তাদের গ্যারান্টিতে:

DeferQueue
পরে চলেহ্যাঁহ্যাঁ
আলাদা প্রসেসে চলেনাহ্যাঁ
প্রসেস মারা গেলেও টিকে থাকেনাহ্যাঁ
Retry সাপোর্টনাহ্যাঁ
Worker/ইনফ্রা লাগেনাহ্যাঁ
সবচেয়ে ভালোহালকা post-response কাজভারী বা ক্রিটিক্যাল কাজ

Defer করুন

  • নন-ক্রিটিক্যাল ইমেইল
  • লগিং ও অ্যানালিটিক্স
  • Cache warming
  • হালকা external কল
  • যার ফল ইউজারের দেখার দরকার নেই

Queue ব্যবহার করুন

  • কাজ ক্রিটিক্যাল, অবশ্যই শেষ হতে হবে
  • Retry বা failure handling লাগবে
  • এত ভারী যে প্রসেস শাটডাউন দেরি হবে
  • সফল হলো কি না দেখার দরকার

সিদ্ধান্ত নেওয়ার গাইড

  1. ইউজারের কি এই ফলাফল এখনই দেখা দরকার?
    হ্যাঁ → রিকোয়েস্টের ভেতরেই করুন। না → পরের প্রশ্ন।
  2. কাজটা হারিয়ে গেলে কি বড় ক্ষতি? (পেমেন্ট, ইনভয়েস, OTP)
    হ্যাঁ → Queue। না → পরের প্রশ্ন।
  3. কাজটা কি কয়েক সেকেন্ডের বেশি নেয়?
    হ্যাঁ → Queue। না → defer()।
  4. প্রতিটি রিকোয়েস্টের শেষে লগ/মেট্রিক্স?
    → Terminable middleware।
  5. ভারী service যা সব রিকোয়েস্টে লাগে না?
    → Deferred provider।

মনে রাখুন

“এখন করো” আর “পরে করো”-এর পার্থক্য — অ্যাপ বানানোর সময় নেওয়া সবচেয়ে সহজ আর কার্যকর সিদ্ধান্তগুলোর একটি।

পুরো লারাভেলে Deferring

কোথায়APIকখন চলে
Middlewareterminate()রেসপন্সের পর
যেকোনো কোডdefer(fn)রেসপন্সের পর
Job / EventdispatchAfterResponse()রেসপন্সের পর
HTTP ClientHttp::batch()->defer()রেসপন্সের পর
Service ProviderDeferrableProviderপ্রথমবার দরকার হলে
QueueShouldQueueআলাদা worker প্রসেসে

মূল কথা

প্রতিটি ভিন্ন সমস্যা সমাধান করে, কিন্তু আইডিয়া এক: অপ্রয়োজনীয় কাজ critical path থেকে সরাও, যাতে মূল flow দ্রুত থাকে।

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

  • Deferring = আগে রেসপন্স, বাকি কাজ পরে।
  • defer() সবচেয়ে সহজ; terminate() প্রতিটি রিকোয়েস্টের শেষে।
  • After-response কাজ একই প্রসেসে — retry বা persistence নেই।
  • ক্রিটিক্যাল বা ভারী কাজ → queue।
  • Deferred provider boot টাইম কমায়।

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

পেমেন্ট সফল হওয়ার পর ইনভয়েস ইমেইল — defer() নাকি queue?

Queue। ইনভয়েস ক্রিটিক্যাল; প্রসেস মারা গেলে হারানো যাবে না, আর retry লাগতে পারে।

Blog post-এর ভিউ কাউন্ট বাড়ানো?

defer() — হালকা, নন-ক্রিটিক্যাল, ইউজারের ফলাফল দেখার দরকার নেই।

dispatchAfterResponse() দিয়ে পাঠানো job কি $tries মানবে?

না। এটা queue worker-এ যায় না, একই প্রসেসে একবার চলে — retry নেই।