অধ্যায় ৯
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) হতে হবে, যাতে রেসপন্স আগে ফ্লাশ হয়।
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(); |
রেসপন্সের পর 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() কল লারাভেলকে বলে — রিকোয়েস্টের সময় নয়, রেসপন্স পাঠানোর পরে ব্যাচটা চালাও।
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-এ সব provider লোড না করে, লারাভেল deferred provider স্কিপ করে যতক্ষণ না তাদের সার্ভিস চাওয়া হয়। Boot টাইম কমে, যে কোড একটি রিকোয়েস্টে কখনো লাগবে না তা লোড হয় না।
Docs: Deferred ProvidersDefer বনাম Queue
দুটোই কাজ পরে করে। পার্থক্য তাদের গ্যারান্টিতে:
| Defer | Queue | |
|---|---|---|
| পরে চলে | হ্যাঁ | হ্যাঁ |
| আলাদা প্রসেসে চলে | না | হ্যাঁ |
| প্রসেস মারা গেলেও টিকে থাকে | না | হ্যাঁ |
| Retry সাপোর্ট | না | হ্যাঁ |
| Worker/ইনফ্রা লাগে | না | হ্যাঁ |
| সবচেয়ে ভালো | হালকা post-response কাজ | ভারী বা ক্রিটিক্যাল কাজ |
Defer করুন
- নন-ক্রিটিক্যাল ইমেইল
- লগিং ও অ্যানালিটিক্স
- Cache warming
- হালকা external কল
- যার ফল ইউজারের দেখার দরকার নেই
Queue ব্যবহার করুন
- কাজ ক্রিটিক্যাল, অবশ্যই শেষ হতে হবে
- Retry বা failure handling লাগবে
- এত ভারী যে প্রসেস শাটডাউন দেরি হবে
- সফল হলো কি না দেখার দরকার
সিদ্ধান্ত নেওয়ার গাইড
- ইউজারের কি এই ফলাফল এখনই দেখা দরকার?
হ্যাঁ → রিকোয়েস্টের ভেতরেই করুন। না → পরের প্রশ্ন। - কাজটা হারিয়ে গেলে কি বড় ক্ষতি? (পেমেন্ট, ইনভয়েস, OTP)
হ্যাঁ → Queue। না → পরের প্রশ্ন। - কাজটা কি কয়েক সেকেন্ডের বেশি নেয়?
হ্যাঁ → Queue। না →defer()। - প্রতিটি রিকোয়েস্টের শেষে লগ/মেট্রিক্স?
→ Terminable middleware। - ভারী service যা সব রিকোয়েস্টে লাগে না?
→ Deferred provider।
মনে রাখুন
“এখন করো” আর “পরে করো”-এর পার্থক্য — অ্যাপ বানানোর সময় নেওয়া সবচেয়ে সহজ আর কার্যকর সিদ্ধান্তগুলোর একটি।
পুরো লারাভেলে Deferring
| কোথায় | API | কখন চলে |
|---|---|---|
| Middleware | terminate() | রেসপন্সের পর |
| যেকোনো কোড | defer(fn) | রেসপন্সের পর |
| Job / Event | dispatchAfterResponse() | রেসপন্সের পর |
| HTTP Client | Http::batch()->defer() | রেসপন্সের পর |
| Service Provider | DeferrableProvider | প্রথমবার দরকার হলে |
| Queue | ShouldQueue | আলাদা 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 নেই।