İçeriğe atla

2025-09-04

Circuit Breaker Pattern: Zincirleme Hataları Önleyen Dayanıklı Mikroservisler

Dağıtık sistemlerde zincirleme hataları önlemek için gerçek dünyadan Circuit Breaker pattern implementasyonu ve kanıtlanmış stratejiler

Bir payment servisi yavaş fail ettiğinde tüm platformu etkileyebilir. Her request timeout olmak için 30 saniye beklediğinde, 12 farklı serviste trafik sıkışması yaratır. Bu klasik zincirleme hata pattern’idir. Circuit Breaker pattern ile bu problemi nasıl çözdüğümüzü ve bu tür incident’ları çözerken resilience hakkında öğrendiklerimizi paylaşacağım.

Problem: Yavaş Olmak Ölü Olmaktan Kötü

Şunu hayal edin: Payment provider’ınızın API’si yavaş cevap vermeye başlıyor. Down değil, sadece normal 200ms yerine request başına 20-30 saniye alıyor. Servisiniz sadakatle bekliyor. Bu arada gelen request’ler birikiyor. Thread pool’lar tükeniyor. Memory consumption patlıyor. Sonunda sağlıklı servisiniz sağlıksız hale geliyor ve enfeksiyon upstream’e yayılıyor.

Bu pattern tüm platformları öldürebilir. En zorlu kısmı? Monitoring’iniz tüm servislerin “up” olduğunu gösteriyor - sadece cevap vermiyorlar. Health check’ler geçer ama request’ler timeout’a düşer.

Circuit Breaker: Sisteminizin Güvenlik Valfi

Circuit Breaker pattern evinizdeki elektrik sigortası gibi çalışır. İşler ters gittiğinde atar, hasarın yayılmasını önler. Ama evinizdeki sigortadan farklı olarak bu akıllı - problemin düzelip düzelmediğini test edebilir ve otomatik olarak recover edebilir. Opsiyonel half-open state sayesinde servis iyileştiğinde akış yeniden başlar.

Üç State

enum CircuitState {
  CLOSED = 'CLOSED',  // Normal operasyon, request'ler akıyor
  OPEN = 'OPEN',  // Circuit atmış, request'ler hemen fail ediyor
  HALF_OPEN = 'HALF_OPEN' // Servis recover olmuş mu test ediyor
}

Bir kulüpteki bouncer gibi düşünün:

  • CLOSED: “Gelin, her şey yolunda”
  • OPEN: “Kimse giremiyor, içeride problem var”
  • HALF_OPEN: “Bir kişiyle kontrol edeyim güvenli mi”

Gerçek Implementasyon: Gerçekten İşe Yarayan

İşte bu zorlukları ele alan bir circuit breaker implementasyonu. Bu pattern yüksek request hacmi olan servislerde güvenilir olduğunu kanıtlamıştır:

interface CircuitBreakerConfig {
  failureThreshold: number;  // Açılmadan önceki failure sayısı
  successThreshold: number;  // Half-open'dan kapanmak için success sayısı
  timeout: number;  // Request timeout ms cinsinden
  resetTimeout: number;  // Half-open denemeden önce bekleme
  volumeThreshold: number;  // Değerlendirmeden önce min request
  errorThresholdPercentage: number; // Trip için error yüzdesi
}

class CircuitBreaker<T> {
  private state: CircuitState = CircuitState.CLOSED;
  private failureCount = 0;
  private successCount = 0;
  private lastFailureTime?: Date;
  private requestCount = 0;
  private errorCount = 0;
  private window = new RollingWindow(10000); // 10 saniyelik window

  constructor(
    private readonly config: CircuitBreakerConfig,
    private readonly protectedFunction: () => Promise<T>
  ) {}

  async execute(): Promise<T> {
    // Half-open denemeli miyiz kontrol et
    if (this.state === CircuitState.OPEN) {
      if (this.shouldAttemptReset()) {
        this.state = CircuitState.HALF_OPEN;
      } else {
        throw new CircuitOpenError('Circuit breaker OPEN');
      }
    }

    try {
      const result = await this.executeWithTimeout();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private async executeWithTimeout(): Promise<T> {
    return Promise.race([
      this.protectedFunction(),
      new Promise<T>((_, reject) =>
        setTimeout(() => reject(new TimeoutError()), this.config.timeout)
      )
    ]);
  }

  private onSuccess(): void {
    this.failureCount = 0;
    this.window.recordSuccess();

    if (this.state === CircuitState.HALF_OPEN) {
      this.successCount++;
      if (this.successCount >= this.config.successThreshold) {
        this.state = CircuitState.CLOSED;
        this.successCount = 0;
      }
    }
  }

  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = new Date();
    this.window.recordFailure();

    if (this.state === CircuitState.HALF_OPEN) {
      this.state = CircuitState.OPEN;
      this.successCount = 0;
      return;
    }

    // Hem absolute hem percentage threshold'ları kontrol et
    const stats = this.window.getStats();
    if (stats.totalRequests >= this.config.volumeThreshold) {
      const errorRate = (stats.failures / stats.totalRequests) * 100;
      if (errorRate >= this.config.errorThresholdPercentage ||
          this.failureCount >= this.config.failureThreshold) {
        this.state = CircuitState.OPEN;
      }
    }
  }

  private shouldAttemptReset(): boolean {
    return this.lastFailureTime &&
      Date.now() - this.lastFailureTime.getTime() >= this.config.resetTimeout;
  }
}

Production’dan Dersler: Tutorial’ların Söylemediği Şeyler

1. Timeout En Önemli Setting’iniz

Incident pattern analizleri gösteriyor ki çoğu hata (yaklaşık %70) complete failure’lardan değil yavaş response’lardan kaynaklanıyor. Timeout’unuzu agresif ayarlamak yardımcı oluyor:

const config = {
  timeout: 3000,  // 3 saniye - P99'umuz 1.2s, bu problemleri yakalıyor
  // NOT 30000!  // Bu bizi öldürdü. 30s beklemek = thread exhaustion
};

Bir payment servisinden örnek timing:

  • Normal P50: 180ms
  • Normal P99: 1.2s
  • Circuit breaker timeout: 3s
  • Sonuç: Zincirleme hatalarda önemli azalma

2. Half-Open State Tuzağı

Başlarda half-open’a geçer, bir request gönderir, başarılı olur, circuit’i kapatır, sonra full traffic ile hemen tekrar fail ederdik. Çözüm: kapanmadan önce birden fazla success iste.

// Bunu yapma
if (testRequest.succeeded) {
  this.state = CircuitState.CLOSED; // Boom! Full traffic geri geliyor
}

// Bunun yerine bunu yap
if (++this.successCount >= this.config.successThreshold) {
  this.state = CircuitState.CLOSED; // Kademeli recovery
}

3. Retry Logic ile Kombine Et (Ama Dikkatli)

Circuit breaker’lar ve retry’lar feedback loop yaratabilir. Güvenilir bir kombinasyon:

class ResilientClient {
  private circuitBreaker: CircuitBreaker<any>;

  async callWithResilience(request: Request): Promise<Response> {
    // Circuit breaker retry logic'i wrap eder, tersi değil
    return this.circuitBreaker.execute(async () => {
      return await this.retryWithBackoff(request, {
        maxAttempts: 3,
        backoffMs: [100, 200, 400],
        shouldRetry: (error) => {
          // Circuit breaker error'larını retry etme
          if (error instanceof CircuitOpenError) return false;
          // Client error'larını retry etme
          if (error.statusCode >= 400 && error.statusCode < 500) return false;
          return true;
        }
      });
    });
  }
}

4. Doğru Metrik’leri Monitor Et

Neyi track etmeli (önem sırasına göre):

  1. Circuit state değişiklikleri - OPEN’da hemen alert
  2. Reset attempt sonuçları - Failed reset’ler = devam eden problem
  3. Request rejection rate - Business impact metriği
  4. OPEN state’te geçen süre - Reset timeout’u ayarlamaya yardımcı

CloudWatch dashboard’umuz:

// Push ettiğimiz custom metrikler
await cloudwatch.putMetricData({
  Namespace: 'CircuitBreakers',
  MetricData: [
    {
      MetricName: 'StateChange',
      Value: 1,
      Unit: 'Count',
      Dimensions: [
        { Name: 'ServiceName', Value: this.serviceName },
        { Name: 'FromState', Value: oldState },
        { Name: 'ToState', Value: newState }
      ]
    },
    {
      MetricName: 'RejectedRequests',
      Value: rejectedCount,
      Unit: 'Count',
      Dimensions: [{ Name: 'ServiceName', Value: this.serviceName }]
    }
  ]
});

İleri Seviye Pattern’ler: Basic Circuit Breaking’in Ötesinde

Bulkheading: İzole Circuit Breaker’lar

Tüm servis için tek circuit breaker kullanma. Kritik path’leri izole et:

class PaymentService {
  private readonly chargeBreaker = new CircuitBreaker(chargeConfig);
  private readonly refundBreaker = new CircuitBreaker(refundConfig);
  private readonly queryBreaker = new CircuitBreaker(queryConfig);

  async chargeCard(request: ChargeRequest): Promise<ChargeResponse> {
    // Charge failure'ları refund'ları etkilemiyor
    return this.chargeBreaker.execute(() => this.api.charge(request));
  }

  async refundPayment(request: RefundRequest): Promise<RefundResponse> {
    // Charge'lar fail ederken bile refund'lar çalışıyor
    return this.refundBreaker.execute(() => this.api.refund(request));
  }
}

Bu pattern yoğun trafik dönemlerinde bir endpoint overwhelm olduğunda diğerlerinin çalışmaya devam etmesini sağlar.

Fallback Stratejileri

Her failure eşit değil. Bazen graceful degrade edebilirsin:

async getProductRecommendations(userId: string): Promise<Product[]> {
  try {
    return await this.recommendationBreaker.execute(
      () => this.mlService.getRecommendations(userId)
    );
  } catch (error) {
    if (error instanceof CircuitOpenError) {
      // Basit popularity-based recommendation'lara fallback
      return this.getPopularProducts();
    }
    throw error;
  }
}

Circuit Breaker Inheritance

Mikroservisler başka mikroservisleri çağırırken, circuit state’i inherit et:

// API Gateway
if (paymentServiceBreaker.state === CircuitState.OPEN) {
  // Payment'a bağımlı order service'i çağırmayı deneme bile
  return { error: 'Payment service unavailable', status: 503 };
}

Gerçek Dünya Konfigürasyon Örnekleri

Production’da farklı servis tipleri için gerçekten işe yarayan:

// External API (payment provider'lar, third-party servisler)
const externalAPIConfig: CircuitBreakerConfig = {
  failureThreshold: 5,  // 5 ardışık failure
  successThreshold: 2,  // Recovery için 2 success
  timeout: 5000,  // 5 saniye timeout
  resetTimeout: 30000,  // 30s sonra recovery dene
  volumeThreshold: 10,  // Minimum 10 request gerekli
  errorThresholdPercentage: 50  // 50% error rate trip eder
};

// Internal mikroservis
const internalServiceConfig: CircuitBreakerConfig = {
  failureThreshold: 10,  // Daha toleranslı
  successThreshold: 3,
  timeout: 3000,  // Daha hızlı timeout
  resetTimeout: 10000,  // Daha hızlı recovery attempt'leri
  volumeThreshold: 20,
  errorThresholdPercentage: 30  // Error rate'lere daha hassas
};

// Database connection'ları
const databaseConfig: CircuitBreakerConfig = {
  failureThreshold: 3,  // Hızlı trip
  successThreshold: 5,  // Yavaş recover
  timeout: 1000,  // Çok hızlı timeout
  resetTimeout: 5000,  // Hızlı retry
  volumeThreshold: 5,
  errorThresholdPercentage: 20  // Çok hassas
};

Circuit Breaker’ları Test Etmek: Chaos Engineering

Test etmediğin circuit breaker’a güvenemezsin. Chaos testing yaklaşımımız:

describe('Circuit Breaker Chaos Testleri', () => {
  it('kademeli degradation'ı handle etmeli', async () => {
    const scenarios = [
      { latency: 100, errorRate: 0 },  // Normal
      { latency: 500, errorRate: 0.1 },  // Hafif degradation
      { latency: 2000, errorRate: 0.3 }, // Büyük degradation
      { latency: 5000, errorRate: 0.7 }, // Neredeyse failure
    ];

    for (const scenario of scenarios) {
      mockService.setScenario(scenario);
      await runLoadTest(1000); // 1000 request

      const metrics = await breaker.getMetrics();
      if (scenario.errorRate > 0.5) {
        expect(breaker.state).toBe(CircuitState.OPEN);
      }
    }
  });
});

Production’da AWS Fault Injection Simulator kullanarak random failure’lar inject edip circuit breaker’larımızın doğru respond ettiğini verify ediyoruz.

Bize Pahalıya Mal Olan Hatalar

Hata 1: Sadece Client-Side Circuit Breaking

Başta circuit breaker’ları sadece client’larda implement ettik. Server’ın kendisi problem yaşadığında kendini koruyamıyordu:

// Kötü: Client kendini koruyor ama server hala overwhelm
class Client {
  private breaker = new CircuitBreaker();
  async call() { return this.breaker.execute(() => fetch('/api')); }
}

// İyi: Server da kendini koruyor
class Server {
  private downstreamBreaker = new CircuitBreaker();
  async handleRequest(req, res) {
    try {
      const data = await this.downstreamBreaker.execute(() =>
        this.database.query(req.query)
      );
      res.json(data);
    } catch (error) {
      if (error instanceof CircuitOpenError) {
        res.status(503).json({ error: 'Servis geçici olarak kullanılamıyor' });
      }
    }
  }
}

Hata 2: İlgisiz Operasyonlar İçin Circuit Breaker Paylaşmak

“Database operasyonları” için tek circuit breaker’ımız vardı. Write’lar fail ettiğinde read’ler de bloklanıyordu:

// Kötü: Her şey için tek breaker
class UserService {
  private dbBreaker = new CircuitBreaker();

  async getUser(id) {
    return this.dbBreaker.execute(() => db.query('SELECT...'));
  }

  async createUser(data) {
    return this.dbBreaker.execute(() => db.query('INSERT...'));
  }
}

// İyi: Farklı operasyonlar için ayrı breaker'lar
class UserService {
  private readBreaker = new CircuitBreaker(readConfig);
  private writeBreaker = new CircuitBreaker(writeConfig);

  async getUser(id) {
    return this.readBreaker.execute(() => db.query('SELECT...'));
  }

  async createUser(data) {
    return this.writeBreaker.execute(() => db.query('INSERT...'));
  }
}

Hata 3: Business Impact’i Düşünmemek

Tüm servislere eşit davrandık. Sonra metrics collection’ı geçirirken payment processing’i blokadık. O dersi hızlı öğrendik.

Implementasyon Checklist’i

Circuit breaker’ları implement ederken kullanışlı bir checklist:

  • Timeout’u P99 latency’nin 2-3 katına ayarla
  • Half-open’dan kapanmadan önce birden fazla success iste
  • Read/write operasyonları için ayrı breaker’lar implement et
  • Business-critical path’ler için fallback davranışı ekle
  • State değişiklikleri ve rejection’lar için metrik export et
  • Production’dan önce chaos engineering ile test et
  • Timeout ve threshold seçimlerini dokümante et
  • Individual failure’larda değil circuit OPEN’da alert et
  • Konfigürasyonda business priority’yi düşün
  • Instant değil gradual recovery implement et

Son Düşünceler: Hızlı Fail Etmek Hakkında

Önemli bir kavrayış: bazen bir servisin yapabileceği en iyi şey hemen fail etmek. 10ms’de 503 response, 30 saniye sonra timeout’tan çok daha iyidir. Kullanıcılar hızlı retry edebilir, sistem recover olabilir. Thread exhaustion çok daha ciddi problemlere yol açar.

Circuit breaker’lar failure’ları önlemekle ilgili değil - failure’ların yayılmasını önlemekle ilgili. Problem düzeldiğinde gerçekten recover edebilecek kadar sistem sağlığını korumakla ilgili.

Circuit breaker’ları problemi yaşamadan önce implement etmek kriz yönetimini çok daha kolay hale getirir.

İlgili yazılar

Async Backend'ler İçin UX Rehberi: Optimistic, Decoupled, ya da Hiçbiri

Async backend'le çalışan tasarımcılar için pragmatik rehber: üç etkileşim şekli, hangisi ne zaman, ve karşı durmanız gereken dört anti-pattern.

event-drivenstate-managementpatterns+2
SaaS Yetkilendirme için AWS Cognito + Verified Permissions

AWS Cognito ve Verified Permissions ile SaaS yetkilendirme mimarisi. Cedar politika dili, çok kiracılı desenler, JWT token akışı, maliyet analizi ve TypeScript örnekleriyle yaygın hatalar.

authorizationawscognito+4
Harici Yetkilendirme Yönetim Sistemleri: Mimarınız İçin Doğru Platformu Seçmek

AWS Verified Permissions, SpiceDB, OpenFGA, Cerbos ve OPA dahil harici yetkilendirme platformlarının tarafsız değerlendirmesi. Mimari desenler, maliyet analizi ve mühendislik ekipleri için karar çerçevesi.

authorizationsecurityarchitecture+5
Cedar vs Rego vs OpenFGA: Politika Dili Karşılaştırması

Cedar, Rego, OpenFGA DSL ve Cerbos YAML/CEL politika dillerinin derinlemesine teknik karşılaştırması. Söz dizimi, performans kıyaslamaları, biçimsel doğrulama, araç desteği ve her dil için TypeScript entegrasyon örneklerini kapsar.

authorizationsecurityarchitecture+3
SpiceDB vs Auth0 FGA: İlişki Tabanlı Yetkilendirme Karşılaştırması

SpiceDB ve Auth0 FGA (OpenFGA) arasında detaylı bir teknik karşılaştırma -- şema tasarımı, tutarlılık modelleri, dağıtım ve ölçeklenebilirlik açısından farklı tercihler yapan iki Zanzibar tabanlı yetkilendirme sistemi.

authorizationsecurityarchitecture+3