오류 처리 가이드¶
이 가이드는 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 호환 유지
폴백 응답 (백엔드 오류를 파싱할 수 없을 때):
재시도 전략¶
설정¶
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
모범 사례¶
오류 설계¶
- 적절한 HTTP 상태 코드 사용
- 명확하고 실행 가능한 오류 메시지 제공
- 추적을 위한 요청 ID 포함
- 클라이언트에 내부 세부 정보 노출 금지
- 충분한 컨텍스트와 함께 오류 로깅
오류 처리¶
- 백오프와 함께 재시도 로직 구현
- 연쇄 장애 방지를 위해 서킷 브레이커 사용
- 적절한 타임아웃 설정
- 적절한 계층에서 오류 처리
- 복구 불가능한 오류는 빠르게 실패
오류 복구¶
- 자동 복구를 위한 헬스 체크 구현
- 중요한 경로에 대한 폴백 전략 사용
- 오류율과 패턴 모니터링
- 이상에 대한 알림 설정
- 오류 시나리오 및 복구 절차 문서화
참고 문서¶
- 서킷 브레이커 아키텍처 - 자세한 서킷 브레이커 구현
- 모델 폴백 아키텍처 - 모델 폴백 시스템 설계
- 설정 가이드
- 모니터링 가이드
- API 레퍼런스
- 빠른 시작 가이드