# LLM API 재시도 vs 폴백: 모델을 바꾸기 전 5가지 기준

> 일시적이고 부작용 없이 다시 실행할 수 있으며 예산이 남았을 때만 같은 경로를 재시도합니다. 폴백은 동일 업무 계약을 통과한 경로에만 허용합니다.

- URL: https://blog.laozhang.ai/ko/posts/llm-api-retry-vs-fallback-model
- Published: 2026-07-27
- Updated: 2026-07-27
- Author: LaoZhang AI Team (https://blog.laozhang.ai/ko/about)
- Topic: API 가이드
- Tags: LLM API, 재시도, 폴백 모델, AI 안정성

---
같은 LLM API 경로를 재시도하려면 실패가 일시적이고, 호출을 부작용 없이 다시 실행할 수 있으며, 워크플로 전체의 시간·횟수·비용 예산이 남아 있어야 합니다. 다른 모델로 폴백하려면 그 경로가 동일한 입력, 출력 스키마, 도구, 안전, 데이터, 지연, 비용, 품질 테스트를 미리 통과해야 합니다. 조건을 충족하지 못하면 큐, 검증된 축소 응답, 또는 안전한 중단이 더 낫습니다.

![LLM API 재시도와 폴백을 결정하는 다섯 기준](https://blog.laozhang.ai/posts/ko/llm-api-retry-vs-fallback-model/img/cover.webp)

## “몇 번 실패했나”보다 먼저 볼 것

| 기준 | 확인할 질문 | 아니면 |
|---|---|---|
| 오류 책임 | 일시적 네트워크/공급자 문제인가? | 요청, 인증, 정책, 예산 책임을 수정 |
| 커밋 상태 | 사용자에게 보인 출력과 부수 효과 없이 재실행 가능한가? | 상태를 조회·조정하거나 중단 |
| 복구 예산 | 시도 횟수, 경과 시간, 토큰, 비용이 남았나? | 큐 또는 승인된 축소 모드 |
| 폴백 등가성 | 이 워크플로의 테스트 픽스처를 통과했나? | 자동 전환 금지 |
| 경로 상태 | 제한된 상태 점검이 의미 있고 모든 시도가 기록되나? | 서킷 열기 |

폴백은 “다른 모델로 재시도”가 아닙니다. 모델을 바꾸면 context window, structured output, tool calling, 안전 필터, 데이터 경계, 가격과 답변 품질이 함께 달라질 수 있습니다.

## 오류 코드를 행동으로 바로 연결하지 마세요

OpenAI의 현재 [error code guide](https://developers.openai.com/api/docs/guides/error-codes)는 429를 요청 속도 제한과 quota/spend 고갈로 나눕니다. 전자는 전송 속도를 낮춰야 하고 후자는 예산 책임자가 해결해야 합니다. 같은 429라고 똑같이 backoff하면 quota 문제를 숨길 뿐입니다.

운영 정책은 최소한 다음을 구분해야 합니다.

- 공급자 오류 세부 정보가 일시적이라고 분류한 5xx/과부하: 짧고 상한이 있는 backoff+jitter. HTTP 코드만으로 결정하지 않음.
- 짧은 `retry-after`가 있는 rate 429: 대기하고 concurrency를 낮춤.
- quota, credit, spend limit: 동기 재시도 중단. 허용된 백그라운드 작업만 큐로 이동.
- [요청 검증 과정의 400 오류](https://blog.laozhang.ai/ko/posts/aws-claude-400-error): 잘못된 요청 형식이나 컨텍스트 초과가 원인이면 요청을 수정한다.
- 401/403: credential, permission, route owner 수정.
- safety/policy: 덜 엄격한 모델로 우회하지 않음.
- 일부 스트림이나 도구 커밋이 불확실함: 상태를 확인한 뒤 결정.

라이브러리가 이미 무엇을 하는지도 확인해야 합니다. Anthropic [API errors](https://platform.claude.com/docs/en/api/errors)는 공식 SDK가 transient connection, rate-limit, 5xx를 기본 두 번 재시도하고 `retry-after`를 따른다고 설명합니다. Gemini [troubleshooting guide](https://ai.google.dev/gemini-api/docs/troubleshooting)도 SDK 자동 retry를 명시합니다. 애플리케이션의 `maxRetries`만 세면 실제 호출량을 놓칩니다.

## 복구 예산은 한 장의 장부입니다

```text
total workflow attempts =
  initial
  + SDK internal
  + gateway
  + application retry
  + fallback
  + queue redelivery
```

여기에 최대 경과 시간, 추가 token/비용, 허용 품질 저하를 더합니다. 실시간 상담과 야간 batch는 같은 정책을 공유할 이유가 없습니다.

AWS의 [timeouts, retries, backoff with jitter](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/)는 여러 계층의 독립 retry가 부하를 곱하고 과부하 복구를 늦출 수 있음을 설명합니다. 한 계층이 다음 행동을 결정하고, 다른 계층은 실제 attempt를 보고하도록 설계하세요.

```yaml
workflow: catalog-classification
max_total_attempts: 3
max_elapsed_ms: 6000
max_extra_cost_usd: 0.012
replay_safe_until: classification_saved
approved_fallback: compact-classifier
queue_allowed: true
fail_closed_on: [auth, policy, unknown_commit]
```

이 값은 예시입니다. 서비스 SLO와 장애 주입 결과로 정해야 합니다.

## 커밋 뒤에는 투명한 재시도가 아닙니다

클라이언트 timeout은 “아무 일도 없었다”는 증거가 아닙니다.

1. provider가 request를 받기 전이면 replay가 비교적 단순합니다.
2. provider가 받았지만 응답이 없으면 결과는 unknown입니다.
3. 사용자가 첫 token을 봤다면 새 모델의 출력을 자연스럽게 이어 붙일 수 없습니다.
4. tool, DB write, webhook, 메일 같은 작업이 시작됐다면 중복 side effect가 생길 수 있습니다.

상태 변경 작업에는 애플리케이션 operation ID 또는 idempotency key를 두고 `requested → started → committed → acknowledged`를 저장하세요. unknown이면 실제 상태를 조회하고 조정해야 합니다. 두 번째 모델 호출은 상태 조회가 아닙니다.

스트리밍에서는 사용자에게 보이는 첫 이벤트가 없고 도구·DB 부수 효과도 커밋되지 않았음을 확인한 경우에만 상태를 초기화해 승인된 경로로 전환할 수 있습니다. 그 뒤에는 중단 사실을 알리고 사용자가 다시 시작하게 하거나 검증된 재개 프로토콜을 사용해야 합니다.

## 폴백 모델의 등가성 계약

같은 테스트 fixture로 여덟 항목을 검증합니다.

1. 입력: context, image/file, system instruction, locale.
2. 출력: JSON Schema, required field, refusal, truncation.
3. 도구: 이름, argument schema, parallel call, result return, idempotency.
4. 안전: policy, high-impact stop, human escalation.
5. 데이터: region, retention, tenant, allowed data class.
6. 운영: p95/p99 latency, streaming, request ID, status.
7. 비용: 단위, cache, quota owner, maximum spend.
8. 품질: workflow별 eval과 합격 기준.

분류 작업에 적합한 작은 모델이 정책 안내에도 적합하다는 보장은 없습니다. 생성형 폴백이 합격하지 못하면 cache, deterministic rule, 또는 명확한 retry-later가 낫습니다. 반대로 답이 정해진 분류 판단만 떼어 맡길 모델이 필요하다면 [선택지와 확률을 돌려주는 Jev 결정 모델](https://blog.laozhang.ai/ko/posts/jev-ai-model-guide)이 있지만, 문장을 생성하는 폴백 역할은 하지 못합니다.

## 정책 코드를 business `catch` 밖으로

```ts
type Action = "retry" | "fallback" | "queue" | "degrade" | "fail_closed";

function decide(x: {
  owner: "transient" | "rate" | "quota" | "request" | "auth" | "policy" | "unknown";
  commitState: "none" | "committed" | "unknown";
  attemptsLeft: number;
  elapsedMsLeft: number;
  costLeft: number;
  retryAfterMs: number;
  retryExpectedMs: number;
  retryExpectedCost: number;
  fallbackExpectedMs: number;
  fallbackExpectedCost: number;
  primaryRoute: "healthy" | "degraded" | "open";
  fallbackRoute: "healthy" | "unhealthy";
  fallbackApproved: boolean;
  queueAllowed: boolean;
  degradeApproved: boolean;
}): Action {
  if (["request", "auth", "policy"].includes(x.owner)) return "fail_closed";
  if (x.commitState !== "none") return "fail_closed";
  if (x.owner === "unknown") return x.queueAllowed ? "queue" : "fail_closed";

  const canRetry =
    x.attemptsLeft > 0 &&
    x.elapsedMsLeft >= x.retryAfterMs + x.retryExpectedMs &&
    x.costLeft >= x.retryExpectedCost;
  const canFallback =
    x.attemptsLeft > 0 &&
    x.elapsedMsLeft >= x.fallbackExpectedMs &&
    x.costLeft >= x.fallbackExpectedCost;

  if (canRetry && ["transient", "rate"].includes(x.owner) && x.primaryRoute !== "open") {
    return "retry";
  }
  if (
    canFallback &&
    ["transient", "rate", "quota"].includes(x.owner) &&
    x.fallbackApproved &&
    x.fallbackRoute === "healthy"
  ) return "fallback";
  if (x.queueAllowed) return "queue";
  return x.degradeApproved ? "degrade" : "fail_closed";
}
```

`retryAfterMs`는 주 경로 재시도에만 적용합니다. 폴백은 자체 예상 시간·비용으로 판단하고, `fallbackApproved`에는 대체 경로의 독립 quota 확인도 포함합니다. 따라서 주 경로의 긴 `retry-after`가 남은 SLO 안에 끝낼 수 있는 정상 폴백을 막지 않습니다. `committed` 또는 `unknown` 상태는 자동 분기 밖에서 대조하거나 검증된 재개/멱등성 프로토콜을 사용해야 합니다. 다음 네트워크 전송 전에 시도 레코드를 먼저 저장하세요. 500도 긴 입력 컨텍스트 같은 요청 원인일 수 있으므로 오류 세부 정보 없이 일시적 장애로 분류하면 안 됩니다.

## staging에서 실패를 만들어 보세요

속도 제한 429, 할당량 429, 스트림 전 503, 토큰 출력 뒤 연결 끊김, 도구 커밋 뒤 타임아웃, 폴백 스키마 누락, 열린 서킷을 주입합니다. 또한 주 경로의 `retry-after`가 60초이고 남은 SLO가 8초이지만, 승인된 정상 폴백은 자체 시간·비용 예산으로 2초 안에 끝나는 사례를 넣습니다. 이때 예상 동작은 큐가 아니라 `fallback`이어야 합니다. 각 테스트 사례는 예상 동작이 하나여야 합니다.

지표는 primary success, retry recovery, fallback recovery, degraded response, fail closed로 분리합니다. 최종 success rate만 보면 고장 난 primary가 폴백 뒤에 숨습니다.

현재 문제가 특정 provider의 429라면 먼저 [OpenAI API rate limit](https://blog.laozhang.ai/ko/posts/openai-api-rate-limit), [Claude API rate limit](https://blog.laozhang.ai/ko/posts/claude-rate-exceeded-error), [Gemini API rate limits](https://blog.laozhang.ai/ko/posts/gemini-api-rate-limits-guide)에서 owner를 확인하세요.

승인된 여러 모델을 하나의 compatible endpoint로 시험해야 한다면 staging에서 최신 [LaoZhang AI API 문서](https://docs.laozhang.ai/)를 확인할 수 있습니다. 단일 endpoint는 adapter를 줄일 수 있지만 schema, tools, 데이터, 비용, 품질 등가성과 복구 예산을 대신 증명하지 않습니다.

*안내: [laozhang.ai는 이 블로그 운영팀의 자사 서비스입니다](https://blog.laozhang.ai/ko/about#laozhang). 그 가격은 독립적인 비교가 아니라 자사 제시 가격입니다.*

완료 기준은 단순한 200이 아닙니다. 모든 실패가 설명 가능한 하나의 action으로 분기되고, 모든 계층이 하나의 예산을 공유하며, 폴백이 동일 업무 계약을 통과하고, 최종 성공이 앞선 실패 기록을 지우지 않아야 합니다.

## 참고 자료

이 글이 링크한 외부 페이지를 본문에 나온 순서대로 정리했습니다. 마지막 업데이트: 2026-07-27.

- [error code guide](https://developers.openai.com/api/docs/guides/error-codes) (developers.openai.com)
- [API errors](https://platform.claude.com/docs/en/api/errors) (platform.claude.com)
- [troubleshooting guide](https://ai.google.dev/gemini-api/docs/troubleshooting) (ai.google.dev)
- [timeouts, retries, backoff with jitter](https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/) (aws.amazon.com)
