Day 25 — Circuit Breaker Pattern — একটা service-এর ব্যর্থতায় পুরো system যেন না পড়ে 🔌

July 17, 20266 min read30 Days Of Backend Engineering

আপনার application, আপনার business সবকিছুই আরামসে চলছে। এখন আপনি লাখপতি থেকে কোটিপতি হয়ে গেছেন। এতদিনে আপনি একটা জিনিস বুঝে গেছেন। server not found error বা server timeout error থেকে server is under maintenance error টা তুলনামূলক ভাবে ভালো।

এখন আপনার application-এ তেমন critical error নেই। যা ছিল সব solve হয়ে গেছে।

কিন্তু মাঝে মাঝে কয়েকটা incident হচ্ছে যেখানে আপনার server crash করছে নানা কারণে। এই জিনিসটা এখন solve করা দরকার। এখন এখানে auto recover করার system-টা on করতে হবে। কিন্তু problem হচ্ছে, system জানবে কীভাবে যে system recover করেছে। আর এই recover করার সময়েই তো user-দেরকে stop করতে হবে। তারা যেন server-এ access করতে না পারে। access করলে তো আবার problem হয়ে যেতে পারে। recover fail করতে পারে। তখন আবার problem।

এর একটা ব্যবস্থা করা দরকার। বিষয়টা এমন না যে করতেই হবে। কারণ এই case-গুলো মাঝে মাঝে rarely হয়ে থাকে। তবে এতে user-এর experience নষ্ট হবে।

এই problem-এর solution already বিদ্যমান, Circuit Breaker।


Circuit Breaker কীভাবে কাজ করে?

আমরা সবাই মোটামুটি electric circuit breaker সম্পর্কে জানি। জানার কথা। অনেক জায়গায় এটাকে main switch-ও বলে। এই electric circuit breaker-এর কাজ হচ্ছে, যখন প্রয়োজনের তুলনায় load বেশি হবে তখন connection-টাই বন্ধ করে দেবে। এতে করে কোনো unwanted situation create-ই হবে না। for example, বৃষ্টির সময় বজ্রপাত হলে এইসব circuit breaker-গুলো connection বন্ধ করে দেয়। এতে electric device-গুলো protect হয়ে যায়।

electrical circuit breaker-এর মতো করেই software circuit breaker কাজ করে। যখন system-এ কোনো problem হয় তখন সেটা protect করে। application-কে recover হওয়ার সময় দেয়।

এখানে তিনটা state:

  • Closed (Normal) — এই state-এর মানে হচ্ছে circuit close আছে, request normal way-তে যাবে। কোনো request fail হলে request-গুলো count হবে। এই failure count একটা threshold cross করলে circuit open state-এ চলে যাবে।
  • Open (Tripped) — এই state-এর মানে হচ্ছে circuit open হয়ে গেছে। আর কোনো request-ই process হবে না। direct fallback return হবে। এই state-এ আরেকটা কাজ করা হয়ে থাকে, এখানে একটা সময় পরে যেয়ে circuit half-open state-এ চলে আসবে।
  • Half-Open (Testing) — এই state সবচেয়ে বেশি important। এই state-এ মূলত check হয় যে system recover করেছে কি না। যদি recover হয়ে থাকে তাহলে circuit-এ আবার closed state-এ চলে যাবে। আর যদি fail হয় তাহলে open state-এ চলে যাবে এবং আরেকটু বেশি সময় wait করবে। এই waiting time-টা generally exponential হয়ে থাকে।

Basic Implementation

এখানে একটা simple circuit breaker implement করা হয়েছে। এটা simple একটা। আপনার requirement অনুযায়ী আপনি আপনার মতো করে বানিয়ে নিতে পারেন। কীভাবে করবেন সেটা আপনার requirement এবং decision-এর উপর depend করছে।

// nestjs-opossum বা custom implementation
class CircuitBreaker {
  private state: "CLOSED" | "OPEN" | "HALF_OPEN" = "CLOSED";
  private failureCount = 0;
  private failureThreshold = 5;
  private recoveryTimeout = 30000; // 30s
  private lastFailureTime = 0;
  private isTestingRecovery = false; // Single-trial guard for HALF_OPEN

  async execute<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T> {
    const now = Date.now();

    // 1. OPEN to HALF_OPEN Transition Check
    if (this.state === "OPEN") {
      if (now - this.lastFailureTime > this.recoveryTimeout) {
        this.state = "HALF_OPEN";
      } else {
        if (fallback) return fallback();
        throw new Error("Circuit is OPEN");
      }
    }

    // 2. HALF_OPEN State: Allow only ONE trial request
    if (this.state === "HALF_OPEN") {
      if (this.isTestingRecovery) {
        // Another probe request is already in-flight; fallback for others
        if (fallback) return fallback();
        throw new Error("Circuit is recovering (HALF_OPEN)");
      }
      this.isTestingRecovery = true;
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      if (fallback) return fallback();
      throw error;
    } finally {
      if (this.state !== "HALF_OPEN") {
        this.isTestingRecovery = false;
      }
    }
  }

  private onSuccess() {
    this.failureCount = 0; // Reset counter on success!
    this.state = "CLOSED";
    this.isTestingRecovery = false;
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    this.isTestingRecovery = false;

    if (
      this.state === "HALF_OPEN" ||
      this.failureCount >= this.failureThreshold
    ) {
      this.state = "OPEN";
    }
  }
}

এখানে আপনাকে প্যারা নেয়ার দরকার নেই। বানাতে সময় লেগে যেতে পারে। bug থাকার possibility তো থাকেই। এর জন্য existing অনেক library আছে যেখানে অনেক feature already exist করে। for example,

  • opossum in nodejs
  • resilience4j in java
  • sony/gobreaker in golang
  • failsafe-rs in rust
  • pybreaker in python

Fallback: Graceful Degradation

Circuit open হলে কী return করব?

এই question-টা circuit breaker-এর implementation-ই change করে দিবে। এখানে আপনাকেই decision নিতে হবে যে আসলে আপনি কী return করবেন। এখানে fallback strategy-তে কিছু option আছে:

  • Return static/cached/default/partial data
  • Return exception with error message
  • Inform user about the issue
  • Show skeleton/loading state
এখন আপনি কোনটা use করবেন সেটা আপনার application-এর উপর depend করছে। for example,
  • একটা blog site-এ আপনি চাইলেই cached data fallback হিসেবে দিতে পারেন।
  • ecommerce-এর ক্ষেত্রে cached data use করা যেতে পারে। কারণ product-এর details তো আর খুব একটা change হয় না। তবে এখানে আপনার requirement-এর উপর depend করবে যে আপনি কী করবেন।
  • banking-এর ক্ষেত্রে আপনি এখন কিন্তু cached data fallback হিসেবে দিতে পারেন না। কারণ এখানে current data-টা খুবই important। এখানে হয় error return করে user-কে inform করাটা better হবে।
এই রকম আরও situation-এ পড়তে পারেন। এইসব জিনিসটা আপনার requirement এবং application-এর উপর depend করবে। এখানে কোনো boundary নেই। আপনার যেটা দরকার সেটাই করবেন। NO worries।

এখানে just simple একটা fallback দিয়ে code generate করা হয়েছে।

const productData = await circuitBreaker.execute(
  () => productService.getDetails(productId),
  () => ({ name: "Product", price: 0, cached: true }), // fallback
);

আপনি যখন partial বা cached data fallback হিসেবে use করছেন তখন কিন্তু user কিছু একটা দেখতে পাচ্ছে। application-টা full blank হয়ে যাবে না। এই জিনিসটাকে বলে Graceful Degradation


Retry Pattern: বুদ্ধিমানের মতো retry

উপরে একটা জিনিসের হালকা কথা বলেছিলাম যে, একটা সময় পর circuit breaker check করবে যে system ঠিক আছে কি না। fail করলে একটা সময় পরে আবার retry করতে হবে। এই retry করার time-টা এমনভাবে use করতে হবে যেন আপনার system recover হওয়ার আগেই fail করে না যায়। এই retry-এর time-টা most of the time exponential time use করা হয়ে থাকে। এতে করে server enough time পাবে recover করার জন্য। নিচের example-টা থেকে বিষয়টা বুঝতে পারবেন।

Exponential Backoff — প্রতিবার wait বাড়াও: 1st retry: 1 second পর 2nd retry: 2 seconds পর 3rd retry: 4 seconds পর 4th retry: 8 seconds পর

অনেক ক্ষেত্রে exponential backoff use করার পরও server recover হয় না। তখন একটা maximum time set করে দিতে হয়, যাতে করে server-এর ওপর বেশি pressure না পড়ে। আবার অনেক সময় অনেকগুলো client একসাথে request send করে তখন আরেকটা issue হতে পারে। সেক্ষেত্রে jitter technique use করা যেতে পারে।


বটম লাইন

Microservice আর distributed system-এ failure inevitable। Circuit Breaker, Retry — এই pattern গুলো failure-কে gracefully handle করে। একটা dependent service-এর failure পুরো system ফেলে দিতে পারবে না।

আপনার system-এ কোন resilience pattern ব্যবহার করছেন? কমেন্টে শেয়ার করুন! 👇