অধ্যায় ৩

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 ✗ Database Sync অ্যাপ চালু ✓

Redis ডাউন? Job যাক database queue-তে। SMTP unreachable? অন্য প্রোভাইডার দিয়ে পাঠাও। প্রাইমারি রিকভার করা পর্যন্ত অ্যাপ চলতে থাকে।

লারাভেল তিনটি সাবসিস্টেমে failover দেয়: Queue, Cache, Mail।

Queue Failover

Queue connection ফেইল করলে (ধরুন Redis offline) job dispatch পুরো বন্ধ হয়ে যাক — এটা চান না। লারাভেলের failover queue driver দিয়ে fallback connection-এর একটি চেইন ডিফাইন করা যায়:

config/queue.php
'connections' => [
    'redis' => [
        'driver' => 'redis',
        // ...
    ],
    'database' => [
        'driver' => 'database',
        // ...
    ],
    'failover' => [
        'driver' => 'failover',
        'connections' => [
            'redis',
            'database',
            'sync',
        ],
    ],
],
.env
QUEUE_CONNECTION=failover

কীভাবে কাজ করে

  1. প্রথমে redis-এ job push করার চেষ্টা।
  2. ফেইল করলে (Redis unavailable) অটোমেটিক্যালি database।
  3. সেটাও ফেইল করলে 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 দায়িত্ব নেয়:

config/cache.php
'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-এর মধ্যে পালাক্রমে পাঠায়।

Docs: Mail Failover

বোনাস: Database Read/Write

এই অংশটি মূল বইয়ে নেই — একই আইডিয়ার আরেকটি উদাহরণ হিসেবে যোগ করা হয়েছে।

বড় অ্যাপে লোড কমাতে Read আর Write সার্ভার আলাদা থাকে। read.host-এ একাধিক হোস্ট দিলে লারাভেল এগুলো থেকে র‍্যান্ডমভাবে একটি বেছে কানেক্ট করে; কানেক্ট না হলে লিস্টের পরেরটিতে চেষ্টা করে।

config/database.php
'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 কানেকশন থেকে হবে। ফলে সদ্য লেখা ডেটা সাথে সাথে পড়া যায়।

Docs: Read & Write Connections

কেন এটা Cross-Cutting

Queue, Cache আর Mail-এর failover তিনটি আলাদা ফিচার নয় যেগুলো কাকতালীয়ভাবে একরকম দেখতে। এটা একটিই কনসেপ্ট:

সাবসিস্টেমকনফিগ কীFailover কী রক্ষা করে
QueueconnectionsRedis বা DB queue ফেইল করলেও job dispatch চলে
CachestoresStore আউটেজেও ক্যাশিং চলে
Mailmailersপ্রোভাইডার আউটেজেও ইমেইল যায়

প্রতিবার একই কাঠামো

ক্রমানুসারে 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অন্য রিসোর্সে সুইচপ্রাইমারি সিস্টেম ডাউন
SES Retry ৩ বার তবুও ফেইল Postmark-এ Failover

মূল কথা

বাস্তবে প্রায়ই দুটোই চান। Retry মুহূর্তের গ্লিচ সামলায়; failover সামলায় আউটেজ।

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

  • Failover = অন্য রিসোর্সে সুইচ; Retry = একই রিসোর্সে আবার চেষ্টা।
  • Queue (QUEUE_CONNECTION), Cache (CACHE_STORE), Mail (MAIL_MAILER) — সবখানে failover driver/transport।
  • Fallback queue connection-এর জন্য আলাদা worker চালাতে ভুলবেন না।
  • Lock/rate limiter কখনো failover cache-এ নয়।
  • QueueFailedOver / CacheFailedOver event লগ করুন।

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

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 রাখার লাভ কী?

কোনো ইমেইল নীরবে হারায় না — লগে রেকর্ড থাকে, পরে আবার পাঠানো যায়।