서킷 브레이커 패턴이란
서킷 브레이커는 전기 회로의 차단기에서 영감을 받은 패턴으로, 외부 서비스 호출이 반복적으로 실패할 때 더 이상의 요청을 차단하여 시스템 전체의 연쇄 장애를 방지합니다.
상태 전이 다이어그램
| 상태 | 동작 | 전이 조건 |
|---|---|---|
| CLOSED | 요청 정상 통과 | 실패율 임계치 초과 → OPEN |
| OPEN | 요청 즉시 거부 (빠른 실패) | 타임아웃 경과 → HALF_OPEN |
| HALF_OPEN | 제한된 요청만 통과 | 성공 → CLOSED / 실패 → OPEN |
Node.js 구현
class CircuitBreaker {
constructor(fn, options = {}) {
this.fn = fn;
this.state = 'CLOSED';
this.failureCount = 0;
this.successCount = 0;
this.lastFailureTime = null;
this.threshold = options.threshold || 5;
this.timeout = options.timeout || 30000;
this.halfOpenMax = options.halfOpenMax || 3;
}
async call(...args) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime >= this.timeout) {
this.state = 'HALF_OPEN';
this.successCount = 0;
console.log('[CircuitBreaker] OPEN → HALF_OPEN');
} else {
throw new Error('Circuit is OPEN — request blocked');
}
}
try {
const result = await this.fn(...args);
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
if (this.state === 'HALF_OPEN') {
this.successCount++;
if (this.successCount >= this.halfOpenMax) {
this.state = 'CLOSED';
this.failureCount = 0;
console.log('[CircuitBreaker] HALF_OPEN → CLOSED');
}
}
this.failureCount = Math.max(0, this.failureCount - 1);
}
onFailure() {
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
console.log('[CircuitBreaker] → OPEN (failures:', this.failureCount, ')');
}
}
}
실전 사용 예시
const axios = require('axios');
const paymentBreaker = new CircuitBreaker(
async (orderId, amount) => {
const res = await axios.post('https://payment-api.example.com/charge', {
orderId, amount,
}, { timeout: 5000 });
return res.data;
},
{ threshold: 3, timeout: 60000 }
);
app.post('/api/orders/:id/pay', async (req, res) => {
try {
const result = await paymentBreaker.call(req.params.id, req.body.amount);
res.json(result);
} catch (error) {
if (error.message.includes('Circuit is OPEN')) {
res.status(503).json({
error: '결제 서비스가 일시적으로 불가합니다',
retryAfter: 60,
});
} else {
res.status(500).json({ error: '결제 처리 중 오류 발생' });
}
}
});
라이브러리 활용
// opossum 라이브러리 사용
const CircuitBreaker = require('opossum');
const breaker = new CircuitBreaker(callExternalService, {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000,
volumeThreshold: 10,
});
breaker.on('open', () => console.log('Circuit opened'));
breaker.on('halfOpen', () => console.log('Circuit half-open'));
breaker.on('close', () => console.log('Circuit closed'));
// 폴백 정의
breaker.fallback(() => ({ cached: true, data: getCachedData() }));
- 서킷 브레이커는 마이크로서비스 간 연쇄 장애(cascade failure)를 방지합니다
- 폴백(fallback) 전략을 함께 구현하여 서비스 품질 저하를 최소화합니다
- 헬스체크 엔드포인트와 결합하면 자동 복구 판단이 정확해집니다
- 모니터링과 알림을 붙여 OPEN 상태 전환 시 즉시 인지할 수 있어야 합니다
서킷 브레이커는 분산 시스템의 복원력(resilience)을 위한 핵심 패턴입니다. 단순한 재시도보다 한 단계 진화한 장애 대응 전략으로, 프로덕션 환경에서 반드시 고려해야 합니다.
댓글 0