본문 바로가기
Backend2025년 8월 20일7분 읽기

서킷 브레이커 패턴 — 장애 전파 방지 실전 구현

YS
김영삼
조회 435

서킷 브레이커 패턴이란

서킷 브레이커는 전기 회로의 차단기에서 영감을 받은 패턴으로, 외부 서비스 호출이 반복적으로 실패할 때 더 이상의 요청을 차단하여 시스템 전체의 연쇄 장애를 방지합니다.

상태 전이 다이어그램

상태동작전이 조건
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

아직 댓글이 없습니다.
Ctrl+Enter로 등록