콘텐츠로 이동

오류 처리 가이드

이 가이드는 Continuum Router의 오류 처리 메커니즘, 상태 코드, 재시도 전략 및 문제 해결을 다룹니다.

목차

오류 범주

클라이언트 오류 (4xx)

수정 없이는 재시도하면 안 되는 잘못된 클라이언트 요청으로 인한 오류입니다.

범주 설명 예제
유효성 검사 오류 잘못된 요청 형식 또는 매개변수 필수 필드 누락, 잘못된 JSON
인증 오류 인증 또는 권한 부여 실패 잘못된 API 키, 만료된 토큰
리소스 오류 요청한 리소스를 찾을 수 없음 사용할 수 없는 모델, 엔드포인트를 찾을 수 없음
속도 제한 오류 너무 많은 요청 쿼터 초과, 속도 제한 도달

서버 오류 (5xx)

일시적이고 재시도 가능할 수 있는 서버 측 오류입니다.

범주 설명 예제
백엔드 오류 백엔드 서비스 문제 연결 거부, 백엔드 타임아웃
라우터 오류 내부 라우터 문제 설정 오류, 패닉
인프라 오류 인프라 장애 데이터베이스 다운, 네트워크 파티션
용량 오류 리소스 고갈 메모리 제한, 연결 풀 가득 참

HTTP 상태 코드

성공 코드 (2xx)

상태 이름 설명 사용 시기
200 OK 요청이 성공적으로 완료됨 일반 응답
201 Created 리소스가 성공적으로 생성됨 리소스 생성
202 Accepted 처리를 위해 요청이 수락됨 비동기 작업
204 No Content 응답 본문 없이 성공 삭제

클라이언트 오류 코드 (4xx)

상태 이름 설명 사용 시기
400 Bad Request 잘못된 요청 형식 잘못된 JSON, 누락된 필드
401 Unauthorized 인증 필요 누락되거나 잘못된 인증
403 Forbidden 접근 거부 불충분한 권한
404 Not Found 리소스를 찾을 수 없음 모델/엔드포인트 사용 불가
405 Method Not Allowed HTTP 메서드 지원되지 않음 잘못된 HTTP 동사
408 Request Timeout 클라이언트 요청 타임아웃 느린 클라이언트
413 Payload Too Large 요청 본문이 너무 큼 크기 제한 초과
422 Unprocessable Entity 유효성 검사 실패 비즈니스 로직 오류
429 Too Many Requests 속도 제한 초과 속도 제한

서버 오류 코드 (5xx)

상태 이름 설명 사용 시기
500 Internal Server Error 예상치 못한 서버 오류 처리되지 않은 예외
501 Not Implemented 기능이 구현되지 않음 지원되지 않는 작업
502 Bad Gateway 잘못된 백엔드 응답 백엔드 오류
503 Service Unavailable 서비스가 일시적으로 다운 모든 백엔드 비정상
504 Gateway Timeout 백엔드 타임아웃 백엔드가 너무 느림
507 Insufficient Storage 스토리지 가득 참 디스크/메모리 가득 참

오류 응답 형식

표준 오류 응답

{
  "error": {
    "code": 404,
    "type": "model_not_found",
    "message": "정상 백엔드에서 모델 'gpt-5'를 찾을 수 없습니다",
    "details": {
      "requested_model": "gpt-5",
      "available_models": ["gpt-4", "gpt-3.5-turbo", "llama2"],
      "backends_checked": 3,
      "healthy_backends": 2
    },
    "request_id": "req_12345",
    "timestamp": "2024-01-15T10:30:45Z"
  }
}

유효성 검사 오류 응답

{
  "error": {
    "code": 400,
    "type": "validation_error",
    "message": "잘못된 요청 매개변수",
    "details": {
      "validation_errors": [
        {
          "field": "messages",
          "error": "필수 필드 누락"
        },
        {
          "field": "temperature",
          "error": "0과 2 사이여야 합니다",
          "value": 3.5
        }
      ]
    },
    "request_id": "req_12346"
  }
}

속도 제한 오류 응답

{
  "error": {
    "code": 429,
    "type": "rate_limit_exceeded",
    "message": "API 속도 제한 초과",
    "details": {
      "limit": 100,
      "window": "1m",
      "retry_after": 45,
      "reset_at": "2024-01-15T10:31:30Z"
    },
    "headers": {
      "X-RateLimit-Limit": "100",
      "X-RateLimit-Remaining": "0",
      "X-RateLimit-Reset": "1705316490",
      "Retry-After": "45"
    }
  }
}

백엔드 오류 전달

백엔드가 4xx 오류를 반환하면 Continuum Router는 백엔드의 원본 오류 세부 정보를 파싱하고 전달합니다. 그래서 디버깅에 더 유용한 오류 정보를 제공할 수 있습니다.

지원되는 백엔드 형식:

  • OpenAI API ({"error": {"message": "...", "type": "...", "param": "...", "code": "..."}})
  • Anthropic Claude API ({"error": {"message": "...", "type": "..."}})
  • Google Gemini API ({"error": {"message": "...", "status": "...", "code": ...}})

예제 응답 (백엔드 오류 전달 포함):

{
  "error": {
    "code": 400,
    "type": "invalid_request_error",
    "message": "잘못된 크기 '512x512'. gpt-image-1에 유효한 크기: 1024x1024, 1536x1024, 1024x1536, auto",
    "param": "size"
  }
}

동작:

  • 백엔드가 파싱 가능한 오류 응답을 반환하면 원본 message, type, param, code 필드가 보존됨
  • 백엔드가 제공할 때 param 필드가 포함됨 (어떤 매개변수가 오류를 일으켰는지 식별하는 데 유용)
  • 백엔드 응답을 파싱할 수 없으면 일반적인 오류 메시지 반환
  • 모든 오류 응답은 OpenAI 호환 유지

폴백 응답 (백엔드 오류를 파싱할 수 없을 때):

{
  "error": {
    "code": 400,
    "type": "invalid_request_error",
    "message": "Client error: HTTP 400"
  }
}

재시도 전략

설정

retry:
  # 기본 설정
  max_attempts: 3
  initial_delay: "100ms"
  max_delay: "10s"
  backoff_multiplier: 2.0
  jitter: true

  # 재시도 가능 조건
  retryable_status_codes:
        - 429  # Too Many Requests
        - 502  # Bad Gateway
        - 503  # Service Unavailable
        - 504  # Gateway Timeout

  retryable_errors:
        - ConnectionError
        - TimeoutError
        - TemporaryError

  # 엔드포인트별 설정
  endpoints:
    "/v1/chat/completions":
      max_attempts: 5
      timeout: "60s"
    "/v1/completions":
      max_attempts: 3
      timeout: "30s"

지수 백오프

backoff:
  type: exponential
  base: 100ms
  multiplier: 2
  max: 10s
  jitter: 0.1  # ±10% 무작위화

# 지연 계산:
# delay = min(base * multiplier^attempt + jitter, max)
#
# 시도 1: 100ms
# 시도 2: 200ms
# 시도 3: 400ms
# 시도 4: 800ms
# ...

스마트 재시도 로직

smart_retry:
  # 다른 백엔드로 재시도
  try_different_backend: true

  # 재시도 시 요청 축소
  reduce_on_retry:
    max_tokens: 0.8  # 20% 감소
    temperature: 0.9  # 온도 낮춤

  # 특정 오류에 대한 재시도 건너뛰기
  non_retryable:
        - AuthenticationError
        - ValidationError
        - PaymentRequired

HTTP 429 재시도 분류

업스트림 HTTP 429가 모두 재시도할 가치가 있는 것은 아닙니다. 라우터는 두 가지 유형을 구분합니다.

일시적 속도 제한 — 업스트림이 분당 요청 수 또는 토큰 수 제한을 적용했지만 계정 자체는 정상 상태입니다. 이 경우에는 일반 백오프로 재시도합니다.

  • OpenAI rate_limit_exceeded / rate_limit_error
  • 크레딧 고갈 언어 없이 발생하는 Google RESOURCE_EXHAUSTED (예: "Quota exceeded for quota metric 'Requests per minute'")
  • 파싱 가능한 본문이 없는 429 (기본적으로 재시도)

비일시적 할당량/청구 고갈 — 업스트림 계정에 사용 가능한 크레딧이나 할당량이 없어 동일 엔드포인트 재시도가 소용없고 부하만 가중시킵니다. 단일 업스트림 호출 후 즉시 실패합니다.

  • OpenAI insufficient_quota 또는 billing_hard_limit_reached 오류 코드/유형
  • 크레딧 고갈 메시지가 포함된 Google RESOURCE_EXHAUSTED (예: "prepayment credits are depleted")

비일시적 429인 경우, 라우터는 제공업체의 원래 상태 코드와 본문을 즉시 반환합니다. 크로스 프로바이더 폴백은 영향받지 않으며, 폴백 체인이 구성된 경우 체인의 다음 모델을 정상적으로 시도합니다.

업스트림 Retry-After 전파

일시적 429를 재시도할 때, 라우터는 고정 지수 스케줄 대신 업스트림 Retry-After 힌트를 백오프 지연으로 사용합니다.

  • 기본 채팅 경로의 정수 초 단위 Retry-After 응답 헤더에서 힌트를 읽습니다.
  • Google 백엔드의 경우, 오류 본문의 구조화된 RetryInfo.retryDelay 필드(예: "30s")가 있으면 파싱하여 사용합니다.
  • 비정상적인 업스트림이 라우터를 과도하게 지연시키지 못하도록 힌트는 max_delay로 제한됩니다.
  • 업스트림 힌트는 클라이언트 측 Retry-After 응답 헤더에 그대로 전달되어 호출자가 자체 재시도를 올바르게 예약할 수 있습니다.

업스트림이 요청한 지연이 요청의 timeout 예산을 초과하면, 라우터는 슬립 없이 즉시 실패를 반환합니다.

서킷 브레이커

아키텍처 세부 정보

상태 머신 다이어그램, 관리자 엔드포인트, Prometheus 메트릭을 포함한 자세한 구현 아키텍처는 서킷 브레이커 아키텍처를 참조하세요.

설정

circuit_breaker:
  enabled: true

  # 장애 감지
  failure_threshold: 5        # 회로 개방까지의 실패 횟수
  success_threshold: 2        # 회로 닫힘까지의 성공 횟수

  # 타이밍
  timeout: "30s"              # 요청 타임아웃
  half_open_timeout: "15s"    # 반개방 상태 지속 시간
  reset_timeout: "60s"        # 재시도까지의 시간

  # 모니터링 윈도우
  window_size: "60s"
  min_requests: 10            # 통계를 위한 최소 요청

회로 상태

stateDiagram-v2
    [*] --> Closed
    Closed --> Open: 장애 임계값 도달
    Open --> HalfOpen: 리셋 타임아웃 만료
    HalfOpen --> Closed: 성공 임계값 도달
    HalfOpen --> Open: 장애 감지

백엔드별 서킷 브레이커

backends:
    - name: primary
    url: http://primary:8000
    circuit_breaker:
      failure_threshold: 3
      reset_timeout: "30s"

    - name: secondary
    url: http://secondary:8000
    circuit_breaker:
      failure_threshold: 5
      reset_timeout: "60s"

오류 복구

자동 복구

recovery:
  # 헬스 체크 복구
  health_checks:
    interval: "30s"
    recovery_threshold: 2  # 연속 성공 횟수

  # 연결 풀 복구
  connection_pool:
    validation_interval: "60s"
    evict_invalid: true
    replace_invalid: true

  # 캐시 복구
  cache:
    clear_on_error: false
    partial_invalidation: true

모델 폴백

라우터는 기본 모델을 사용할 수 없을 때 자동 모델 폴백을 지원합니다. 오류 복구 흐름에서 서킷 브레이커와 함께 동작합니다.

# 모델 폴백 설정
fallback:
  enabled: true

  fallback_chains:
    "gpt-4o":
      - "gpt-4-turbo"
      - "gpt-3.5-turbo"
    "claude-opus-4-5-20251101":
      - "claude-sonnet-4-5"
      - "claude-haiku-4-5"
    # 크로스 프로바이더 폴백
    "gemini-2.5-pro":
      - "gemini-2.5-flash"
      - "gpt-4o"

  fallback_policy:
    trigger_conditions:
      error_codes: [429, 500, 502, 503, 504]
      timeout: true
      connection_error: true
      circuit_breaker_open: true
    max_fallback_attempts: 3
    fallback_timeout_multiplier: 1.5

폴백 트리거 조건

조건 HTTP 상태 설명
Rate Limit 429 백엔드 속도 제한 초과
Server Error 500 내부 백엔드 오류
Bad Gateway 502 백엔드에서 잘못된 응답
Service Unavailable 503 백엔드가 일시적으로 사용 불가
Gateway Timeout 504 백엔드 요청 타임아웃
Circuit Open N/A 서킷 브레이커가 열림

폴백 응답 헤더

폴백이 발생하면 다음 헤더가 추가됩니다:

X-Fallback-Used: true
X-Original-Model: gpt-4o
X-Fallback-Model: gpt-4-turbo
X-Fallback-Reason: error_code_429
X-Fallback-Attempts: 2

폴백 오류 응답

모든 폴백이 소진되었을 때:

{
  "error": {
    "code": 503,
    "type": "all_fallbacks_exhausted",
    "message": "'gpt-4o'에 대한 모든 폴백 모델 실패",
    "details": {
      "original_model": "gpt-4o",
      "attempted_fallbacks": ["gpt-4-turbo", "gpt-3.5-turbo"],
      "failure_reasons": [
        {"model": "gpt-4-turbo", "reason": "error_code_503"},
        {"model": "gpt-3.5-turbo", "reason": "timeout"}
      ]
    },
    "request_id": "req_12345"
  }
}

우아한 성능 저하

degradation:
  # 폴백 모델 (레거시 - fallback.fallback_chains 사용 권장)
  model_fallbacks:
    "gpt-4": ["gpt-3.5-turbo", "gpt-3"]
    "claude-opus": ["claude-sonnet", "claude-haiku"]

  # 기능 저하
  features:
    streaming:
      fallback_to_non_streaming: true
    functions:
      disable_on_error: true

  # 응답 저하
  response:
    reduce_max_tokens: true
    lower_temperature: true
    simplify_prompts: true

장애 조치 전략

failover:
  strategy: priority  # 또는 round-robin, least-failures

  backends:
        - name: primary
      priority: 1
      weight: 100

        - name: secondary
      priority: 2
      weight: 50

        - name: tertiary
      priority: 3
      weight: 10

  conditions:
        - error_rate > 0.1
        - latency_p99 > 5s
        - health_score < 0.5

사용자 정의 오류 처리

오류 미들웨어

// 사용자 정의 오류 핸들러 구현
pub async fn error_handler(error: Error) -> Response {
    let (status, error_type, message) = match error {
        Error::Validation(e) => (
            StatusCode::BAD_REQUEST,
            "validation_error",
            e.to_string()
        ),
        Error::NotFound(e) => (
            StatusCode::NOT_FOUND,
            "not_found",
            e.to_string()
        ),
        Error::Backend(e) => (
            StatusCode::BAD_GATEWAY,
            "backend_error",
            "Backend service error"
        ),
        Error::Internal(e) => {
            error!("Internal error: {:?}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                "internal_error",
                "An internal error occurred"
            )
        }
    };

    Json(ErrorResponse {
        error: ErrorDetails {
            code: status.as_u16(),
            error_type: error_type.to_string(),
            message: message.to_string(),
            request_id: request_id(),
            timestamp: Utc::now(),
        }
    }).into_response()
}

오류 변환

error_transformation:
  # 백엔드 오류를 클라이언트 친화적 메시지로 매핑
  mappings:
        - backend_error: "CUDA out of memory"
      client_error: "모델을 일시적으로 사용할  없습니다. 다시 시도해 주세요"
      status: 503

        - backend_error: "Model not loaded"
      client_error: "모델 초기화 진행 중"
      status: 503
      retry_after: 30

  # 민감한 정보 숨기기
  sanitization:
    remove_stack_traces: true
    remove_internal_ips: true
    remove_credentials: true

오류 훅

error_hooks:
  # 오류 전 훅
  pre_error:
        - log_error
        - capture_metrics
        - notify_monitoring

  # 오류 후 훅
  post_error:
        - cleanup_resources
        - update_circuit_breaker
        - trigger_failover

  # 사용자 정의 핸들러
  handlers:
        - type: webhook
      url: https://alerts.example.com/errors
      events: [critical_error, repeated_error]

        - type: email
      to: oncall@example.com
      events: [service_down]

오류 디버깅

오류 로깅

logging:
  errors:
    level: debug  # 모든 오류를 상세히 로깅
    include_request_body: true
    include_response_body: true
    include_headers: true

  # 구조화된 오류 로깅
  format:
    type: json
    fields:
      - timestamp
      - level
      - error_type
      - error_message
      - request_id
      - backend_id
      - model
      - latency
      - stack_trace

디버그 엔드포인트

# 최근 오류 가져오기
curl http://localhost:8080/admin/errors/recent

# 오류 통계 가져오기
curl http://localhost:8080/admin/errors/stats

# 특정 오류 세부 정보 가져오기
curl http://localhost:8080/admin/errors/req_12345

# 테스트용 오류 트리거
curl -X POST http://localhost:8080/admin/debug/error \
  -d '{"type": "backend_timeout", "backend": "primary"}'

오류 트레이싱

tracing:
  errors:
    capture_stack_trace: true
    capture_variables: true
    capture_context: true

  # 분산 트레이싱
  propagation:
        - tracecontext
        - baggage

  # 오류 샘플링
  sampling:
    all_errors: true
    error_rate_threshold: 0.01

일반적인 오류 시나리오

시나리오 1: 모든 백엔드 다운

# 감지
condition: all_backends.health_status == unhealthy

# 응답
response:
  status: 503
  message: "서비스가 일시적으로 사용할  없습니다"
  retry_after: 30

# 복구
recovery:
    - increase_health_check_frequency
    - notify_oncall
    - attempt_backend_restart
    - switch_to_backup_region

시나리오 2: 모델을 찾을 수 없음

# 감지
condition: requested_model not in available_models

# 응답
response:
  status: 404
  message: "모델 '{model}'을 찾을  없습니다"
  suggestions: similar_models

# 완화
mitigation:
    - check_model_aliases
    - refresh_model_cache
    - try_alternative_backends

시나리오 3: 속도 제한 초과

# 감지
condition: request_count > rate_limit

# 응답
response:
  status: 429
  retry_after: upstream_hint_or_backoff()
  headers:
    Retry-After: <seconds>  # 업스트림에서 제공된 경우 전달

# 처리 — 일시적 속도 제한 (RPM/TPM 스로틀링)
handling:
    - honor_upstream_retry_after_hint
    - cap_delay_to_max_delay
    - fail_fast_if_delay_exceeds_timeout_budget

# 처리 — 비일시적 (할당량/청구 고갈)
handling:
    - fail_fast_after_single_call
    - pass_through_provider_error_body
    - cross_provider_fallback_if_chain_configured

라우터는 재시도 여부를 결정하기 전에 업스트림 429 응답을 일시적 또는 비일시적으로 분류합니다. 전체 규칙은 HTTP 429 재시도 분류를 참조하세요.

시나리오 4: 타임아웃

# 감지
condition: request_duration > timeout

# 응답
response:
  status: 504
  message: "요청 타임아웃"

# 완화
mitigation:
    - try_with_reduced_max_tokens
    - switch_to_faster_backend
    - enable_streaming_if_possible

시나리오 5: 백엔드 불일치

# 감지
condition: backend_response_format != expected_format

# 응답
response:
  status: 502
  message: "잘못된 백엔드 응답"

# 복구
recovery:
    - log_response_for_debugging
    - mark_backend_as_degraded
    - retry_with_different_backend
    - update_backend_adapter

시나리오 6: 파일 해결 실패

# 감지
condition: file_reference_not_found OR invalid_file_id

# 응답
response:
  status: 404  # 파일을 찾을 수 없음
  # 또는
  status: 400  # 잘못된 파일 ID 형식
  message: "파일 참조 해결 실패"
  type: "invalid_request_error"
  code: "file_resolution_failed"

# 세부 정보
details:
    - file_id: "file-abc123"
    - reason: "file not found" OR "invalid file ID format"

# 완화
mitigation:
    - verify_file_was_uploaded
    - check_file_id_format_starts_with_file_prefix
    - ensure_file_not_deleted

시나리오 7: 파일이 너무 큼

# 감지
condition: file_size > max_file_size

# 응답
response:
  status: 413
  message: "파일 크기가 최대 허용량을 초과합니다"
  type: "invalid_request_error"

# 세부 정보
details:
  file_size: 600000000  # 바이트
  max_size: 536870912   # 바이트 (512MB 기본값)

# 완화
mitigation:
    - compress_file_before_upload
    - use_smaller_file
    - increase_max_file_size_in_config

시나리오 8: 너무 많은 파일 참조

# 감지
condition: file_references_count > 20

# 응답
response:
  status: 400
  message: "요청에 파일 참조가 너무 많습니다"
  type: "invalid_request_error"

# 세부 정보
details:
  count: 25
  max_allowed: 20

# 완화
mitigation:
    - split_request_into_multiple_calls
    - reduce_number_of_files

오류 모니터링

메트릭

# 오류율
rate(http_requests_total{status=~"5.."}[5m])

# 유형별 오류율
rate(errors_total[5m]) by (error_type)

# 백엔드 오류율
rate(backend_errors_total[5m]) by (backend_id, error_type)

# 서킷 브레이커 상태
circuit_breaker_state{backend_id="primary"}

알림

alerts:
    - name: HighErrorRate
    condition: error_rate > 0.05
    duration: 5m
    severity: warning

    - name: AllBackendsDown
    condition: healthy_backends == 0
    duration: 1m
    severity: critical

    - name: CircuitBreakerOpen
    condition: circuit_breaker_state == "open"
    duration: 5m
    severity: warning

모범 사례

오류 설계

  1. 적절한 HTTP 상태 코드 사용
  2. 명확하고 실행 가능한 오류 메시지 제공
  3. 추적을 위한 요청 ID 포함
  4. 클라이언트에 내부 세부 정보 노출 금지
  5. 충분한 컨텍스트와 함께 오류 로깅

오류 처리

  1. 백오프와 함께 재시도 로직 구현
  2. 연쇄 장애 방지를 위해 서킷 브레이커 사용
  3. 적절한 타임아웃 설정
  4. 적절한 계층에서 오류 처리
  5. 복구 불가능한 오류는 빠르게 실패

오류 복구

  1. 자동 복구를 위한 헬스 체크 구현
  2. 중요한 경로에 대한 폴백 전략 사용
  3. 오류율과 패턴 모니터링
  4. 이상에 대한 알림 설정
  5. 오류 시나리오 및 복구 절차 문서화

참고 문서