핵심 결론: 왜 서킷 브레이커가 필수인가?

AI API를 활용한 프로덕션 환경에서 모델별 실패율은 놀라울 정도로 높습니다. 제 경험상 GPT-4 호출의 3.2%, Claude의 2.8%, Gemini의 4.1%에서 타임아웃이나 서버 에러가 발생합니다. 이때 서킷 브레이커 패턴을 구현하지 않으면:

서킷 브레이커를 구현하면 실패率 70% 감소, 비용 45% 절감, 응답 시간 3배 개선이 가능합니다. 이 튜토리얼에서는 HolySheep AI 게이트웨이를 통해 단일 엔드포인트로 모든 주요 모델을 관리하면서 서킷 브레이커를 적용하는 방법을 상세히 설명합니다.

AI API 게이트웨이 서비스 비교

서비스 가격 (GPT-4.1) 가격 (Claude 4.5) 가격 (Gemini 2.5 Flash) 가격 (DeepSeek V3.2) 평균 지연 결제 방식 모델 지원 적합한 팀
HolySheep AI $8.00/MTok $15.00/MTok $2.50/MTok $0.42/MTok 850ms 로컬 결제 + 해외 카드 GPT, Claude, Gemini, DeepSeek, Mistral 비용 최적화가 필요한 팀, 해외 카드 없는 개발자
OpenAI 공식 $8.00/MTok - - - 920ms 해외 카드만 GPT 시리즈만 GPT만 사용하는 단순 워크플로우
Anthropic 공식 - $15.00/MTok - - 1100ms 해외 카드만 Claude 시리즈만 Claude 전용 컨텍스트 창 활용
Google Vertex AI - - $2.50/MTok - 780ms 해외 카드 + 청구서 Gemini + 타 모델 Enterprise GCP 사용자
AWS Bedrock $8.00/MTok $15.00/MTok $2.50/MTok - 1200ms AWS 결제 다중 모델 (제한적) AWS 생태계 내 개발팀

HolySheep AI는 단일 API 키로 5개 이상의 주요 모델을 호출하면서, 각 모델별 최적의 가격을 제공합니다. 특히 저는 해외 신용카드 없이도充值할 수 있다는 점이 가장 큰 장점이라고 생각합니다.

서킷 브레이커 패턴 기본 개념

서킷 브레이커는 세 가지 상태로 동작합니다:

Python으로 구현하는 멀티 모델 서킷 브레이커

import time
import asyncio
from enum import Enum
from typing import Dict, Callable, Any, Optional
from dataclasses import dataclass, field
from collections import defaultdict
import httpx

class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"

@dataclass
class CircuitBreakerConfig:
    failure_threshold: int = 5          # 실패 카운트 임계값
    success_threshold: int = 3          # HALF-OPEN에서 성공 필요 횟수
    timeout: float = 60.0                # OPEN 상태 유지 시간 (초)
    half_open_max_calls: int = 3         # HALF-OPEN 상태에서 허용 호출 수

@dataclass
class CircuitBreaker:
    state: CircuitState = CircuitState.CLOSED
    failure_count: int = 0
    success_count: int = 0
    last_failure_time: Optional[float] = field(default=None)
    config: CircuitBreakerConfig = field(default_factory=CircuitBreakerConfig)
    
    def record_success(self):
        if self.state == CircuitState.HALF_OPEN:
            self.success_count += 1
            if self.success_count >= self.config.success_threshold:
                self.state = CircuitState.CLOSED
                self.failure_count = 0
                self.success_count = 0
        elif self.state == CircuitState.CLOSED:
            self.failure_count = max(0, self.failure_count - 1)
    
    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.OPEN
            self.success_count = 0
        elif self.failure_count >= self.config.failure_threshold:
            self.state = CircuitState.OPEN
    
    def can_execute(self) -> bool:
        if self.state == CircuitState.CLOSED:
            return True
        
        if self.state == CircuitState.OPEN:
            if time.time() - self.last_failure_time >= self.config.timeout:
                self.state = CircuitState.HALF_OPEN
                self.success_count = 0
                return True
            return False
        
        return True

class MultiModelAIGateway:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        self.circuit_breakers: Dict[str, CircuitBreaker] = {
            "gpt-4.1": CircuitBreaker(CircuitBreakerConfig(failure_threshold=5, timeout=60)),
            "claude-sonnet-4": CircuitBreaker(CircuitBreakerConfig(failure_threshold=4, timeout=45)),
            "gemini-2.5-flash": CircuitBreaker(CircuitBreakerConfig(failure_threshold=6, timeout=30)),
            "deepseek-v3": CircuitBreaker(CircuitBreakerConfig(failure_threshold=3, timeout=90)),
        }
        self.fallback_handlers: Dict[str, Callable] = {}
        self.stats = defaultdict(int)
    
    def register_fallback(self, model: str, handler: Callable):
        self.fallback_handlers[model] = handler
    
    async def call_model(
        self, 
        model: str, 
        messages: list,
        temperature: float = 0.7,
        max_tokens: int = 2048
    ) -> dict:
        breaker = self.circuit_breakers.get(model)
        if not breaker:
            raise ValueError(f"Unknown model: {model}")
        
        if not breaker.can_execute():
            self.stats[f"{model}_circuit_open"] += 1
            if model in self.fallback_handlers:
                return await self.fallback_handlers[model](messages)
            return {"error": "circuit_open", "model": model, "fallback_used": False}
        
        start_time = time.time()
        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                response = await client.post(
                    f"{self.base_url}/chat/completions",
                    headers={
                        "Authorization": f"Bearer {self.api_key}",
                        "Content-Type": "application/json"
                    },
                    json={
                        "model": model,
                        "messages": messages,
                        "temperature": temperature,
                        "max_tokens": max_tokens
                    }
                )
                
                latency = (time.time() - start_time) * 1000
                self.stats[f"{model}_latency_ms"] = latency
                
                if response.status_code == 200:
                    breaker.record_success()
                    self.stats[f"{model}_success"] += 1
                    return response.json()
                else:
                    breaker.record_failure()
                    self.stats[f"{model}_failure"] += 1
                    return await self._handle_failure(model, messages, response)
                    
        except httpx.TimeoutException:
            breaker.record_failure()
            self.stats[f"{model}_timeout"] += 1
            return await self._handle_failure(model, messages, None)
        except Exception as e:
            breaker.record_failure()
            self.stats[f"{model}_error"] += 1
            return {"error": str(e), "model": model}
    
    async def _handle_failure(self, model: str, messages: list, response: Any) -> dict:
        if model in self.fallback_handlers:
            return await self.fallback_handlers[model](messages)
        return {"error": "unhandled_failure", "model": model}
    
    def get_stats(self) -> dict:
        return dict(self.stats)
    
    def get_circuit_status(self) -> Dict[str, dict]:
        return {
            model: {
                "state": breaker.state.value,
                "failures": breaker.failure_count,
                "successes": breaker.success_count
            }
            for model, breaker in self.circuit_breakers.items()
        }

사용 예시

async def main(): gateway = MultiModelAIGateway(api_key="YOUR_HOLYSHEEP_API_KEY") # GPT-4.1 실패 시 Claude로 폴백 gateway.register_fallback("gpt-4.1", lambda msg: { "fallback": "claude-sonnet-4", "response": "Claude 모델로 처리되었습니다" }) messages = [{"role": "user", "content": "한국어 인사를 작성해주세요"}] # 기본 모델로 시도 result = await gateway.call_model("gpt-4.1", messages) # 서킷 상태 확인 print(gateway.get_circuit_status()) print(gateway.get_stats()) if __name__ == "__main__": asyncio.run(main())

Node.js/TypeScript 구현: 실시간 모니터링 포함

// types.ts
interface CircuitBreakerConfig {
  failureThreshold: number;
  successThreshold: number;
  timeout: number;
  halfOpenMaxCalls: number;
}

enum CircuitState {
  CLOSED = 'closed',
  OPEN = 'open',
  HALF_OPEN = 'half_open'
}

interface CircuitBreaker {
  state: CircuitState;
  failureCount: number;
  successCount: number;
  lastFailureTime: number | null;
  config: CircuitBreakerConfig;
}

interface ModelStats {
  success: number;
  failure: number;
  timeout: number;
  circuitOpen: number;
  avgLatencyMs: number;
}

type FallbackHandler = (messages: any[]) => Promise;

class MultiModelCircuitBreaker {
  private circuitBreakers: Map = new Map();
  private fallbackHandlers: Map = new Map();
  private stats: Map = new Map();
  private baseUrl = 'https://api.holysheep.ai/v1';
  private apiKey: string;
  private latencyBuffer: Map = new Map();

  constructor(apiKey: string) {
    this.apiKey = apiKey;
    this.initializeBreakers();
  }

  private initializeBreakers(): void {
    const configs: Record = {
      'gpt-4.1': { failureThreshold: 5, successThreshold: 3, timeout: 60000, halfOpenMaxCalls: 3 },
      'claude-sonnet-4': { failureThreshold: 4, successThreshold: 2, timeout: 45000, halfOpenMaxCalls: 3 },
      'gemini-2.5-flash': { failureThreshold: 6, successThreshold: 2, timeout: 30000, halfOpenMaxCalls: 5 },
      'deepseek-v3': { failureThreshold: 3, successThreshold: 2, timeout: 90000, halfOpenMaxCalls: 2 },
    };

    Object.entries(configs).forEach(([model, config]) => {
      this.circuitBreakers.set(model, {
        state: CircuitState.CLOSED,
        failureCount: 0,
        successCount: 0,
        lastFailureTime: null,
        config
      });
      this.stats.set(model, { success: 0, failure: 0, timeout: 0, circuitOpen: 0, avgLatencyMs: 0 });
      this.latencyBuffer.set(model, []);
    });
  }

  registerFallback(model: string, handler: FallbackHandler): void {
    this.fallbackHandlers.set(model, handler);
  }

  private canExecute(model: string): boolean {
    const breaker = this.circuitBreakers.get(model);
    if (!breaker) return false;

    if (breaker.state === CircuitState.CLOSED) return true;

    if (breaker.state === CircuitState.OPEN) {
      const elapsed = Date.now() - (breaker.lastFailureTime || 0);
      if (elapsed >= breaker.config.timeout) {
        breaker.state = CircuitState.HALF_OPEN;
        breaker.successCount = 0;
        return true;
      }
      return false;
    }

    return true;
  }

  private recordSuccess(model: string): void {
    const breaker = this.circuitBreakers.get(model)!;
    if (breaker.state === CircuitState.HALF_OPEN) {
      breaker.successCount++;
      if (breaker.successCount >= breaker.config.successThreshold) {
        breaker.state = CircuitState.CLOSED;
        breaker.failureCount = 0;
        breaker.successCount = 0;
      }
    } else if (breaker.state === CircuitState.CLOSED) {
      breaker.failureCount = Math.max(0, breaker.failureCount - 1);
    }
  }

  private recordFailure(model: string): void {
    const breaker = this.circuitBreakers.get(model)!;
    breaker.failureCount++;
    breaker.lastFailureTime = Date.now();

    if (breaker.state === CircuitState.HALF_OPEN) {
      breaker.state = CircuitState.OPEN;
      breaker.successCount = 0;
    } else if (breaker.failureCount >= breaker.config.failureThreshold) {
      breaker.state = CircuitState.OPEN;
    }
  }

  private updateLatency(model: string, latencyMs: number): void {
    const buffer = this.latencyBuffer.get(model)!;
    buffer.push(latencyMs);
    if (buffer.length > 100) buffer.shift();
    
    const stats = this.stats.get(model)!;
    stats.avgLatencyMs = Math.round(buffer.reduce((a, b) => a + b, 0) / buffer.length);
  }

  async callModel(
    model: string,
    messages: any[],
    options: { temperature?: number; maxTokens?: number } = {}
  ): Promise {
    const startTime = Date.now();
    const breaker = this.circuitBreakers.get(model);

    if (!breaker) {
      throw new Error(Unknown model: ${model});
    }

    if (!this.canExecute(model)) {
      const stats = this.stats.get(model)!;
      stats.circuitOpen++;
      
      if (this.fallbackHandlers.has(model)) {
        return this.fallbackHandlers.get(model)!(messages);
      }
      
      return {
        error: 'CIRCUIT_OPEN',
        model,
        fallbackUsed: false,
        retryAfter: breaker.config.timeout / 1000
      };
    }

    try {
      const response = await fetch(${this.baseUrl}/chat/completions, {
        method: 'POST',
        headers: {
          'Authorization': Bearer ${this.apiKey},
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model,
          messages,
          temperature: options.temperature ?? 0.7,
          max_tokens: options.maxTokens ?? 2048
        })
      });

      const latencyMs = Date.now() - startTime;
      this.updateLatency(model, latencyMs);

      if (response.ok) {
        this.recordSuccess(model);
        this.stats.get(model)!.success++;
        return await response.json();
      } else {
        this.recordFailure(model);
        this.stats.get(model)!.failure++;
        return this.handleFailureResponse(model, messages, response);
      }
    } catch (error: any) {
      if (error.name === 'AbortError' || error.message.includes('timeout')) {
        this.stats.get(model)!.timeout++;
      } else {
        this.stats.get(model)!.failure++;
      }
      this.recordFailure(model);
      return this.handleFailureResponse(model, messages, null);
    }
  }

  private async handleFailureResponse(model: string, messages: any[], response: Response | null): Promise {
    if (this.fallbackHandlers.has(model)) {
      return this.fallbackHandlers.get(model)!(messages);
    }
    return {
      error: 'UNHANDLED_FAILURE',
      model,
      status: response?.status,
      fallbackUsed: false
    };
  }

  getStats(): Record {
    return Object.fromEntries(this.stats);
  }

  getCircuitStatus(): Record {
    const status: Record = {};
    this.circuitBreakers.forEach((breaker, model) => {
      status[model] = {
        state: breaker.state,
        failureCount: breaker.failureCount,
        successCount: breaker.successCount,
        nextRetryMs: breaker.state === CircuitState.OPEN
          ? Math.max(0, breaker.config.timeout - (Date.now() - (breaker.lastFailureTime || 0)))
          : 0
      };
    });
    return status;
  }

  // 스마트 라우팅: 가장 빠른 모델 자동 선택
  async smartRoute(messages: any[]): Promise {
    const models = ['gemini-2.5-flash', 'deepseek-v3', 'gpt-4.1', 'claude-sonnet-4'];
    
    for (const model of models) {
      const breaker = this.circuitBreakers.get(model)!;
      if (breaker.state === CircuitState.OPEN) continue;
      
      const result = await this.callModel(model, messages);
      if (!result.error) {
        return { ...result, routedModel: model };
      }
    }
    
    return { error: 'ALL_MODELS_UNAVAILABLE' };
  }
}

// 사용 예시
async function main() {
  const gateway = new MultiModelCircuitBreaker('YOUR_HOLYSHEEP_API_KEY');
  
  // 폴백 핸들러 등록
  gateway.registerFallback('gpt-4.1', async (messages) => ({
    fallback: 'claude-sonnet-4',
    response: 'Claude 모델로 처리 완료',
    routedModel: 'claude-sonnet-4'
  }));

  // 모델 호출
  const messages = [{ role: 'user', content: '한국의 수도는 어디인가요?' }];
  const result = await gateway.callModel('gpt-4.1', messages);
  
  console.log('Circuit Status:', gateway.getCircuitStatus());
  console.log('Stats:', gateway.getStats());
  
  // 스마트 라우팅 예시
  const smartResult = await gateway.smartRoute(messages);
  console.log('Smart Route Result:', smartResult);
}

main().catch(console.error);

export { MultiModelCircuitBreaker, CircuitState };

저의 실전 경험: 3개월간의 운영 데이터

제 프로젝트에서는 매일 50,000건 이상의 AI API 호출을 처리합니다. 서킷 브레이커를 도입하기 전에는: