#Circuit Breaker คืออะไร

Circuit Breaker เป็น resilience pattern สำหรับระบบที่ต้องเรียกบริการภายนอก เช่น Payment Gateway, AI/LLM API, REST API หรือ Microservice โดยหยุดส่ง request ชั่วคราวเมื่อปลายทางล้มเหลวต่อเนื่อง แทนที่จะ retry อย่างไม่มีขอบเขต

แนวคิดคล้ายเบรกเกอร์ไฟฟ้า: เมื่อพบความผิดปกติเกิน threshold วงจรจะ "ตัด" เพื่อป้องกันความเสียหายลุกลาม

#3 สถานะหลัก

  1. CLOSED --- ระบบทำงานปกติ request ถูกส่งไปยัง service ปลายทาง
  2. OPEN --- failure ถึง threshold แล้ว ระบบหยุดเรียก service และตอบ fallback ทันที
  3. HALF-OPEN --- หลัง cooldown ระบบอนุญาต probe request จำนวนจำกัดเพื่อทดสอบว่าปลายทางกลับมาหรือยัง
                 failures >= threshold
       ┌─────────────────────────────────────┐
       │                                     ▼
   ┌────────┐                           ┌────────┐
   │ CLOSED │                           │  OPEN  │
   └────────┘                           └────────┘
       ▲                                     │
       │ success                             │ cooldown
       │                                     ▼
       └───────────────────────────────┌───────────┐
                                      │ HALF-OPEN │
                                      └───────────┘
                                           │
                              failure ──────┴───── success
                                OPEN               CLOSED

#Circuit Breaker ต่างจาก Retry อย่างไร

  • Retry เหมาะกับ transient failure และลอง request ใหม่ในช่วงสั้น ๆ
  • Circuit Breaker หยุดเรียก dependency ที่มีแนวโน้มเสียต่อเนื่อง
  • ใน production มักใช้ร่วมกับ timeout + bounded retry + circuit breaker + fallback

ไม่ควร retry ทุก HTTP error เช่น 400/401/403 เพราะมักไม่ใช่ transient failure

#Laravel รองรับอะไรบ้าง

Laravel HTTP Client มี timeout, connectTimeout และ retry สำหรับ outbound HTTP requests แต่ Circuit Breaker สามารถประกอบขึ้นเองด้วย Cache/Redis เพื่อแชร์ state ระหว่าง application instances ได้ นอกจากนี้ queued jobs มี ThrottlesExceptions ซึ่งมีพฤติกรรมใกล้เคียงการลดแรงกระแทกจาก third-party service ที่ไม่เสถียร

#Workshop: สร้าง Circuit Breaker ด้วย Laravel + Redis

#1. ตั้งค่า Cache

ตัวอย่าง .env

CACHE_STORE=redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379

ในระบบหลาย instance ควรใช้ shared cache เช่น Redis เพื่อให้ทุก instance เห็นสถานะ breaker เดียวกัน

#2. สร้าง CircuitBreaker Service

สร้างไฟล์ app/Services/CircuitBreaker.php

<?php

namespace App\Services;

use Closure;
use Illuminate\Support\Facades\Cache;
use RuntimeException;
use Throwable;

class CircuitBreaker
{
    public function __construct(
        private readonly string $name,
        private readonly int $failureThreshold = 5,
        private readonly int $cooldownSeconds = 30,
    ) {}

    private function failuresKey(): string
    {
        return "cb:{$this->name}:failures";
    }

    private function openUntilKey(): string
    {
        return "cb:{$this->name}:open_until";
    }

    public function execute(Closure $operation, ?Closure $fallback = null): mixed
    {
        $openUntil = Cache::get($this->openUntilKey());

        if ($openUntil && now()->timestamp < $openUntil) {
            return $this->fallback($fallback, 'Circuit is OPEN');
        }

        try {
            // เมื่อ cooldown หมด request นี้ทำหน้าที่เป็น probe แบบง่าย
            $result = $operation();

            Cache::forget($this->failuresKey());
            Cache::forget($this->openUntilKey());

            return $result;
        } catch (Throwable $e) {
            $failures = Cache::increment($this->failuresKey());

            // ให้ counter มี TTL เพื่อไม่สะสม failure เก่าไปตลอด
            if ($failures === 1) {
                Cache::put(
                    $this->failuresKey(),
                    1,
                    now()->addMinutes(5)
                );
            }

            if ($failures >= $this->failureThreshold) {
                Cache::put(
                    $this->openUntilKey(),
                    now()->addSeconds($this->cooldownSeconds)->timestamp,
                    $this->cooldownSeconds
                );
            }

            if ($fallback) {
                return $fallback($e);
            }

            throw $e;
        }
    }

    private function fallback(?Closure $fallback, string $message): mixed
    {
        if ($fallback) {
            return $fallback(new RuntimeException($message));
        }

        throw new RuntimeException($message);
    }
}

ตัวอย่างนี้เหมาะสำหรับการเรียนรู้ ใน production ควรใช้ atomic lock สำหรับ HALF-OPEN probe เพื่อป้องกันหลาย instance ยิง probe พร้อมกัน และกำหนด failure policy ให้ชัดเจน

#3. เรียก External API

สร้าง app/Services/PaymentService.php

<?php

namespace App\Services;

use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class PaymentService
{
    public function charge(array $payload): array
    {
        $breaker = new CircuitBreaker(
            name: 'payment-api',
            failureThreshold: 5,
            cooldownSeconds: 30,
        );

        return $breaker->execute(
            operation: function () use ($payload) {
                $response = Http::connectTimeout(2)
                    ->timeout(5)
                    ->retry(2, 200)
                    ->post(config('services.payment.url').'/charge', $payload);

                if ($response->serverError()) {
                    throw new RuntimeException(
                        "Payment API failed: {$response->status()}"
                    );
                }

                // 4xx ควรจัดการเป็น business/client error แยกจาก breaker
                $response->throw();

                return $response->json();
            },
            fallback: fn ($e) => [
                'status' => 'pending',
                'message' => 'Payment service is temporarily unavailable.',
            ],
        );
    }
}

#4. Flow การทำงาน

flowchart TD
    A[Laravel Application] --> B{Circuit State}
    B -->|CLOSED| C[Call External API]
    B -->|OPEN| F[Fallback]
    C -->|Success| D[Reset Failure Counter]
    C -->|Failure| E[Increment Failure Counter]
    E --> G{Threshold reached?}
    G -->|No| H[Return error/fallback]
    G -->|Yes| I[OPEN Circuit]
    I --> J[Cooldown]
    J --> K[HALF-OPEN Probe]
    K -->|Success| D
    K -->|Failure| I

#Production Hardening

ควรเพิ่มองค์ประกอบต่อไปนี้:

  • ใช้ Redis/shared cache เพื่อให้ circuit state สอดคล้องกันทุก node
  • ใช้ atomic lock ตอน HALF-OPEN เพื่อให้มี probe request จำกัด
  • นับเฉพาะ failure ที่บ่งชี้ dependency failure เช่น timeout, connection error, HTTP 5xx และ 429 ตาม policy
  • ไม่เปิด breaker จาก validation error หรือ authentication error โดยอัตโนมัติ
  • ใช้ timeout สั้นและ retry แบบจำกัด พร้อม exponential backoff/jitter เมื่อเหมาะสม
  • ทำ fallback เช่น cached data, queue งานไว้ภายหลัง หรือ degraded response
  • เก็บ metrics: state, failure count, open count, rejected calls, recovery time
  • log state transition CLOSED → OPEN → HALF-OPEN → CLOSED
  • แยก circuit ตาม dependency หรือ endpoint ที่มี failure domain ต่างกัน

#Circuit Breaker กับ Queue

สำหรับ queued jobs ที่เรียก third-party API สามารถใช้ Illuminate\Queue\Middleware\ThrottlesExceptions เพื่อลดความถี่ในการ retry หลังเกิด exception ต่อเนื่อง ซึ่งเหมาะกับ dependency ที่ไม่เสถียร แม้แนวคิดและ API จะไม่เหมือน Circuit Breaker state machine เต็มรูปแบบ

#Architecture ที่แนะนำ

Client
  │
  ▼
Laravel API
  │
  ├── Timeout
  │
  ├── Bounded Retry
  │
  ├── Circuit Breaker ───── Redis
  │         │
  │         ├── CLOSED
  │         ├── OPEN ────── Fallback / Queue / Cache
  │         └── HALF-OPEN
  │
  ▼
External Service

#แนวทางตั้งค่าเริ่มต้น

สำหรับ API ทั่วไปอาจเริ่มทดลองด้วย:

connect timeout : 1–3 s
request timeout : 3–10 s
retry           : 1–2 ครั้ง
failure threshold: 5 ครั้ง
cooldown        : 30–60 s

ค่าจริงควร derive จาก SLO, latency distribution, error budget และพฤติกรรมของ dependency ไม่ควรใช้ค่าชุดเดียวกับทุก service

#สรุป

Circuit Breaker ช่วยให้ Laravel fail fast เมื่อ dependency มีปัญหา ลด request ที่ไร้ประโยชน์ ลด resource exhaustion และป้องกัน cascading failure ในระบบ distributed system

สูตรที่เหมาะสำหรับ production คือ:

Timeout → Bounded Retry → Circuit Breaker → Fallback → Observability

โดย Laravel มี building blocks สำคัญ เช่น HTTP Client, Cache/Redis, atomic locks และ queue exception throttling ที่สามารถนำมาประกอบเป็น resilience layer ได้