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):
- Circuit state değişiklikleri - OPEN’da hemen alert
- Reset attempt sonuçları - Failed reset’ler = devam eden problem
- Request rejection rate - Business impact metriği
- 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'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.
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.
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.
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.
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.