অধ্যায় ৩
Failover Mechanism
"When the primary is down, switch to a different resource entirely."
প্রাইমারি সার্ভিস ডাউন হলে অটোমেটিক্যালি ব্যাকআপে চলে যাওয়া — Queue, Cache আর Mail-এ।
Failover কী?
আপনার অ্যাপ Postmark দিয়ে ইমেইল পাঠায়। এক সকালে Postmark-এ আউটেজ। ইউজাররা পাসওয়ার্ড রিসেট পাচ্ছে না, অর্ডার কনফার্মেশন জমে যাচ্ছে, সাপোর্ট টিকেট ভরে যাচ্ছে।
Postmark-এর আপটাইম আপনার হাতে নেই। ব্যবহারিক সমাধান হলো ‘failover’ — একটি ব্যাকআপ মেইলার, যা প্রাইমারি ডাউন হলে দায়িত্ব নেয়।
Retry থেকে কীভাবে আলাদা?
Retry একই রিসোর্সে একই কাজ আবার চেষ্টা করে। Failover সম্পূর্ণ অন্য একটি রিসোর্সে সুইচ করে। সার্ভার পুরোপুরি ক্র্যাশ করলে বারবার retry করে লাভ নেই — তখন দরকার Plan-B।
Redis ডাউন? Job যাক database queue-তে। SMTP unreachable? অন্য প্রোভাইডার দিয়ে পাঠাও। প্রাইমারি রিকভার করা পর্যন্ত অ্যাপ চলতে থাকে।
লারাভেল তিনটি সাবসিস্টেমে failover দেয়: Queue, Cache, Mail।
Queue Failover
Queue connection ফেইল করলে (ধরুন Redis offline) job dispatch পুরো বন্ধ হয়ে যাক — এটা চান না। লারাভেলের failover queue driver দিয়ে fallback connection-এর একটি চেইন ডিফাইন করা যায়:
'connections' => [
'redis' => [
'driver' => 'redis',
// ...
],
'database' => [
'driver' => 'database',
// ...
],
'failover' => [
'driver' => 'failover',
'connections' => [
'redis',
'database',
'sync',
],
],
],
.env
QUEUE_CONNECTION=failover
কীভাবে কাজ করে
- প্রথমে
redis-এ job push করার চেষ্টা। - ফেইল করলে (Redis unavailable) অটোমেটিক্যালি
database। - সেটাও ফেইল করলে
sync— job রিকোয়েস্টের ভেতরেই চলে যায়।
Failover Event ও সতর্কতা
Failover ঘটলে লারাভেল QueueFailedOver event ফায়ার করে — লগ বা অ্যালার্ট পাঠানোর সুযোগ:
Event::listen(QueueFailedOver::class, function ($event) {
Log::warning('Queue failed over', [
'from' => $event->connectionName,
'job' => $event->job,
]);
});
সাবধান — Fallback connection-এর জন্যও worker লাগবে!
Horizon শুধু Redis-এর সাথে কাজ করে। Fallback database connection-এর জন্য আলাদা queue:work database worker চালাতে হবে। না হলে job সফলভাবে push হবে, কিন্তু টেবিলে পড়ে থাকবে — কখনো প্রসেস হবে না।
# Horizon Redis সামলাবে, আর এটা fallback সামলাবে
php artisan queue:work database
Docs: Queue Failover
Cache Failover
ক্যাশ হারালে অ্যাপ ক্র্যাশ করবে না, কিন্তু সবকিছু ধীর হয়ে যেতে পারে। failover cache store দিয়ে প্রাইমারি unreachable হলে ব্যাকআপ store দায়িত্ব নেয়:
'stores' => [
'redis' => [
'driver' => 'redis',
// ...
],
'database' => [
'driver' => 'database',
'table' => 'cache',
// ...
],
'failover' => [
'driver' => 'failover',
'stores' => [
'redis',
'database',
],
],
],
.env
CACHE_STORE=failover
Redis connection exception দিলে লারাভেল database-এ সুইচ করে; ক্যাশ অপারেশন বাধা ছাড়াই চলে। এটার জন্যও event আছে:
Event::listen(CacheFailedOver::class, function ($event) {
Log::error('Cache store failed over', [
'from' => $event->storeName,
'operation' => $event->operation,
'key' => $event->key,
]);
});
Docs: Cache Failover
Cache Failover: বাস্তবে যা ঘটে
কনফিগে দেখতে সহজ, কিন্তু প্রোডাকশনে আচরণ আরও জটিল।
Failover ≠ Replication
Failover চলাকালীন write শুধু fallback store-এ যায়। প্রাইমারি ফিরে এলে সেই ভ্যালুগুলো তার কাছে নেই — দুই store-এ আলাদা স্টেট। ক্যাশকে disposable ধরলে তবেই এটা গ্রহণযোগ্য।
রিকভারির সমস্যা
Redis ফিরলে লারাভেল সাথে সাথে ফেরত যায়। ফলে stale read, হঠাৎ cache miss, আর key আবার ভরার সময় stampede। remember() / rememberForever() এই ট্রানজিশন মসৃণ করে।
শুধু Hard Failure-এ ট্রিগার
Redis চালু কিন্তু ধীর, ওভারলোডেড বা আংশিক এরর দিচ্ছে — তখন সুইচ হবে না। মাল্টি-সার্ভারে কিছু সার্ভার Redis-এ, কিছু fallback-এ থাকতে পারে — কোনো সমন্বয় নেই।
সবচেয়ে বিপজ্জনক ভুল
ভাবা যে fallback store Redis-এর সব পারে। Atomic increment, নিখুঁত TTL, Lua, lock — file/database cache এগুলো পারে না।
Atomic lock, distributed lock, uniqueness guard, rate limiter, job throttling — এগুলো কখনো failover cache store দিয়ে চালাবেন না। নীরবে আচরণ বদলে সূক্ষ্ম বাগ বা ডেটা নষ্ট হতে পারে।
Cache failover শেষ ভরসা। নিয়মিত fallback-এ চলে গেলে বুঝবেন, মূল ক্যাশ ইনফ্রাস্ট্রাকচার ঠিক করা দরকার।
Mail Failover
ইমেইল প্রোভাইডার ডাউন হয়, SMTP unreachable হয়, API rate limit লাগে। Failover mailer থাকলে প্রাইমারিতে সমস্যা হলেও ইমেইল যায়:
config/mail.php'mailers' => [
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'smtp.example.com'),
// ...
],
'postmark' => [
'transport' => 'postmark',
// ...
],
'failover' => [
'transport' => 'failover',
'mailers' => [
'smtp',
'postmark',
],
],
],
.env
MAIL_MAILER=failover
প্রথমে smtp; ফেইল করলে (connection, authentication, timeout) postmark। সবগুলো ফেইল করলে শেষ exception throw হয়।
একটি প্রোডাকশন সেটআপ
'failover' => [
'transport' => 'failover',
'mailers' => [
'ses', // প্রাইমারি
'postmark', // ব্যাকআপ
'log', // শেষ ভরসা — কোনো মেইল হারাবে না
],
],
log mailer ইউজারের কাছে কিছু পাঠায় না, কিন্তু রেকর্ড থাকে — পরে আবার পাঠানো যায়।
বোনাস: লোড ভাগ করতে চাইলে 'transport' => 'roundrobin' — একাধিক mailer-এর মধ্যে পালাক্রমে পাঠায়।
বোনাস: Database Read/Write
এই অংশটি মূল বইয়ে নেই — একই আইডিয়ার আরেকটি উদাহরণ হিসেবে যোগ করা হয়েছে।
বড় অ্যাপে লোড কমাতে Read আর Write সার্ভার আলাদা থাকে। read.host-এ একাধিক হোস্ট দিলে লারাভেল এগুলো থেকে র্যান্ডমভাবে একটি বেছে কানেক্ট করে; কানেক্ট না হলে লিস্টের পরেরটিতে চেষ্টা করে।
'mysql' => [
'read' => [
'host' => [
'192.168.1.1', // Read replica 1
'192.168.1.2', // Read replica 2
],
],
'write' => [
'host' => [
'192.168.1.3', // Primary (write)
],
],
'sticky' => true,
'driver' => 'mysql',
// ...
],
'sticky' => true — একই রিকোয়েস্টে কিছু write হলে, পরের read-গুলোও write কানেকশন থেকে হবে। ফলে সদ্য লেখা ডেটা সাথে সাথে পড়া যায়।
কেন এটা Cross-Cutting
Queue, Cache আর Mail-এর failover তিনটি আলাদা ফিচার নয় যেগুলো কাকতালীয়ভাবে একরকম দেখতে। এটা একটিই কনসেপ্ট:
| সাবসিস্টেম | কনফিগ কী | Failover কী রক্ষা করে |
|---|---|---|
| Queue | connections | Redis বা DB queue ফেইল করলেও job dispatch চলে |
| Cache | stores | Store আউটেজেও ক্যাশিং চলে |
mailers | প্রোভাইডার আউটেজেও ইমেইল যায় |
প্রতিবার একই কাঠামো
ক্রমানুসারে fallback তালিকা
ফেইল হলে অটো সুইচ
Observability-র জন্য event
ক্র্যাশের বদলে graceful degradation
এটা সম্ভব কারণ সব driver একই contract মেনে চলে — বিস্তারিত অধ্যায় ৮: Driver Pattern-এ।
Best Practices
Failover event মনিটর করুন
সবসময় লগ বা অ্যালার্ট করুন। অ্যাপ চললেও এটা ইনফ্রাস্ট্রাকচার অস্থিরতার সংকেত।
Fallback সাবধানে বাছুন
যে fallback নিজেও ফেইল করে, তা অর্থহীন। ২–৩ স্তরই যথেষ্ট; লম্বা চেইন জটিলতা ও সময় বাড়ায়।
Staging-এ টেস্ট করুন
Redis বন্ধ করুন, মেইল প্রোভাইডারের API ব্লক করুন। যে failover কখনো টেস্ট হয়নি, তাতে ভরসা করা যায় না।
পারফরম্যান্স ট্রেডঅফ মনে রাখুন
DB cache Redis-এর চেয়ে ধীর; sync queue রিকোয়েস্টের ভেতরে চলে; log mailer আসলে মেইল পাঠায় না। এটা degraded state — প্রাইমারি ঠিক করার বিকল্প নয়।
Failover বনাম Retry
দুটো একে অপরের পরিপূরক, কিন্তু আলাদা সমস্যা সমাধান করে:
| প্যাটার্ন | কৌশল | কখন |
|---|---|---|
| Retry | একই রিসোর্সে আবার চেষ্টা | সাময়িক সমস্যা (নেটওয়ার্ক গ্লিচ, ছোট timeout) |
| Failover | অন্য রিসোর্সে সুইচ | প্রাইমারি সিস্টেম ডাউন |
মূল কথা
বাস্তবে প্রায়ই দুটোই চান। Retry মুহূর্তের গ্লিচ সামলায়; failover সামলায় আউটেজ।
সারসংক্ষেপ ও যাচাই
- Failover = অন্য রিসোর্সে সুইচ; Retry = একই রিসোর্সে আবার চেষ্টা।
- Queue (
QUEUE_CONNECTION), Cache (CACHE_STORE), Mail (MAIL_MAILER) — সবখানেfailoverdriver/transport। - Fallback queue connection-এর জন্য আলাদা worker চালাতে ভুলবেন না।
- Lock/rate limiter কখনো failover cache-এ নয়।
QueueFailedOver/CacheFailedOverevent লগ করুন।
নিজেকে যাচাই করুন
Redis ধীর কিন্তু চালু — cache failover কি ট্রিগার হবে?
না। Failover শুধু hard failure (connection exception)-এ সুইচ করে।
Queue failover-এ job database-এ গেল, কিন্তু কখনো চললো না। কেন?
Database connection-এর জন্য কোনো worker চলছে না। Horizon শুধু Redis দেখে; php artisan queue:work database চালাতে হবে।
Mail failover চেইনের শেষে log রাখার লাভ কী?
কোনো ইমেইল নীরবে হারায় না — লগে রেকর্ড থাকে, পরে আবার পাঠানো যায়।