LLM API 장애 대응: 429 오류·재시도·fallback 설계

LLM API의 같은 429도 속도 제한, 할당량, 비용 상한으로 원인이 다르다. 재시도·대기열·fallback·즉시 실패를 나누는 운영 기준과 로그·비용 경계를 정리했다.

읽는 시간 약 13

추천 독자: 개발자

AI는 자료 조사와 초안 정리에 보조적으로 사용했으며, 편집부가 출처와 사실을 확인했습니다.

LLM API 요청을 재시도, 대체 모델, 안전한 중단 경로로 나누는 장애 대응 구조

LLM API 장애 대응에서 가장 먼저 없앨 코드는 if (status === 429) retry()다. 같은 429라도 잠깐 기다리면 풀리는 속도 제한, 제한 기간의 초기화를 기다려야 하는 할당량, 크레딧 충전·한도 변경 또는 다음 제한 기간을 기다려야 하는 비용 상태가 섞여 있다. HTTP 상태만 보고 요청을 다시 보내면 복구가 아니라 장애 시간과 비용을 늘릴 수 있다.

운영 정책의 판단 순서는 HTTP 상태 → 공급사의 기계 판독 코드 → 응답 헤더 → 우리 서비스의 최근 오류율이다. 그 뒤에야 재시도, 대기열 이동, 모델 전환, 즉시 실패 중 하나를 고른다. 2026년 8월 25일 OpenAI·Anthropic·Google 공식 문서를 다시 대조한 결론이다. 아래 횟수와 시간은 고정 정답이 아니라 서비스별로 정해야 할 경계다.

LLM API 장애 대응은 429의 원인부터 가른다

OpenAI 오류 코드 문서는 속도 제한뿐 아니라 credit_balance_exhausted, organization_spend_limit_exceeded, project_spend_limit_exceeded, organization_usage_limit_exceeded도 429로 돌려준다. 앞의 속도 제한은 Retry-After를 따라 다시 보낼 수 있지만 뒤의 네 상태는 즉시 재전송으로 풀리지 않는다. 제한의 초기화 시점이나 크레딧·설정 변경을 먼저 확인해야 한다.

Claude는 HTTP 상태와 세부 코드 조합이 다르다. Claude 속도·비용 한도 문서를 보면 조직이 사용 티어의 월 비용 상한에 닿았을 때 429 rate_limit_error가 온다. 일반 속도 제한과 달리 retry-after가 없고 error.details.error_codeenforced_spend_limit_reached다.

반면 사용자가 직접 낮춰 둔 비용 상한에 닿으면 400 invalid_request_error가 올 수 있다. 429만 재시도하고 400은 모두 버리는 규칙으로는 둘 다 제대로 처리하지 못한다.

Gemini Interactions API는 같은 429 아래에서도 rate_limit_exceededquota_exceeded를 구분한다. Gemini API 오류 표는 전자를 지수 백오프 대상으로, 후자를 일일 할당량 초기화 대기나 상향 요청 대상으로 안내한다. Gemini 한도 문서에 따르면 한도는 API 키가 아니라 프로젝트에 적용된다. 키만 새로 발급해도 같은 프로젝트 한도가 되살아나지는 않는다.

다만 Gemini의 10분 단위 사용액 기반 제한은 시간 구간이 지나면 풀릴 수 있다. ‘비용 관련 429’를 모두 같은 영구 실패로 다루면 안 되는 이유다.

관측한 신호첫 처리다시 보내기 전 확인할 것
연결 오류, 408, 일시적 429, 일시 오류로 확인한 500·503·504, Claude 529제한된 재시도 후보SDK 자동 재시도, Retry-After, 전체 제한 시간
크레딧 소진, 일일 할당량, 월 강제 상한 관련 429현재 요청을 중단error.code, details.error_code, 초기화 시점, 결제·한도 화면
시간 구간형 속도·사용액 제한 429대기 후 제한된 재시도기계 코드, 구간 초기화 시점, 전체 제한 시간
400, 401, 402, 403, 413요청을 실패 처리입력, 키, 권한, 결제, 요청 크기
404경로·리소스·모델 확인모델 종료나 권한 문제인지, 검증한 대체 모델이 있는지
409원인을 본 뒤 조건부 처리리소스 충돌인지 일시 오류인지
안전 차단, 거절, 잘못된 도구 호출인프라 재시도에서 제외입력 수정, 사람 검토, 정책상 허용된 대체 경로
타임아웃, 스트림 중단결과 미확정으로 기록서버 작업 완료 여부와 이미 전달된 출력
JSON 스키마·업무 규칙 실패출력 검증 실패로 분리모델 전환 전에 같은 계약을 지킬 수 있는지

이 표의 핵심은 ‘동일 요청 재전송’과 ‘원인을 고친 뒤 새로 호출’을 구분하는 데 있다. 잘못된 API 키로 401이 났을 때 키를 교체한 뒤 호출하는 것은 재시도 정책이 아니라 설정 복구다.

재시도 횟수보다 전체 예산을 먼저 정한다

OpenAI 속도 제한 문서는 임시 429에 Retry-After가 있으면 그 값을 최소 대기시간으로 쓰고 작은 지터를 더하라고 설명한다. 헤더가 없거나 유효하지 않을 때는 지수 백오프와 지터를 쓴다. 실패한 요청도 분당 한도에 포함되므로 쉬지 않고 다시 보내면 회복이 더 늦어진다.

문제는 SDK가 이미 같은 일을 하고 있다는 점이다. OpenAI Node SDK와 Claude 공식 SDK는 현재 일부 연결 오류와 429, 5xx를 자동 재시도한다. Claude 오류 문서는 기본 두 번이라고 명시한다. Gemini 문제 해결 문서에는 Python SDK가 일시 오류를 최대 네 번 자동 재시도한다고 적혀 있다.

언어와 SDK 버전에 따라 기본값은 달라진다. 애플리케이션 바깥에 반복문을 하나 더 넣기 전에 실제 클라이언트 설정부터 읽어야 한다.

최악의 네트워크 호출 수는 대략 다음처럼 불어난다.

앱의 시도 수 × SDK가 한 시도에서 보내는 최대 요청 수 × 순차 폴백(fallback) 경로 수

예를 들어 각 공급사에서 앱이 최대 세 번 시도하고 SDK가 최초 요청 뒤 두 번 더 보내며 공급사 두 곳을 순차로 훑는다면, 논리 작업 하나가 최대 18번의 네트워크 요청으로 번진다. 기대값이 아니라 상한을 확인하기 위한 계산이다. SDK 재시도 중 하나라도 성공하면 실제 호출 수는 줄어든다.

운영 정책에는 적어도 네 개의 경계를 함께 둔다.

  • 한 공급사에 보낼 최대 전송 횟수
  • 사용자가 기다릴 전체 제한 시간
  • 한 논리 작업이 소비할 입력·출력 토큰과 예상 비용
  • 같은 기능이나 테넌트가 하루에 쓸 수 있는 비용

UI가 있는 고객 기능이라면 짧은 제한 시간 뒤 “잠시 후 다시 시도”나 저장된 결과로 돌아가는 편이 낫다. 밤에 도는 내부 분류 작업이라면 긴 대기와 대기열 재처리가 가능하다. 같은 재시도 숫자를 두 경로에 복사할 이유가 없다.

타임아웃은 실패 확정이 아니다

클라이언트가 연결을 끊었다고 서버의 추론까지 취소됐다고 가정하면 안 된다. 스트리밍은 더 까다롭다. Claude는 HTTP 200 뒤에도 SSE 오류 이벤트가 올 수 있다고 문서에 적었다. 사용자가 이미 앞부분을 받았는데 요청 전체를 다시 보내면 문장이 중복될 수 있다. 실제 스트리밍 이벤트와 완료 상태를 처리하는 코드는 OpenAI·Claude·Gemini TypeScript API 예제에 따로 정리해 두었다.

메일 발송, 결제, 티켓 생성, 데이터베이스 쓰기가 모델 호출 뒤에 붙는다면 작업 ID를 먼저 만든다. support-ticket:order-104:classification-v3처럼 애플리케이션이 이해하는 논리 작업 키로 시도, 완료, 결과 미확정 상태를 연결한다.

이 키와 완료 플래그만으로 ‘한 번만 실행’이 보장되지는 않는다. 동시 실행은 데이터베이스의 unique 제약이나 lock으로 막는다. DB 변경과 후속 작업 예약은 transactional outbox 같은 원자적 경계로 묶는다. 외부 메일·결제 API가 멱등 키나 상태 조회를 제공하면 그 기능을 쓴다. 타임아웃으로 결과가 미확정이면 재전송하기 전에 기존 결과를 조회한다. 공급사별 멱등 기능의 존재와 적용 범위는 사용하는 API 문서에서 따로 확인해야 한다.

응답이 오래 걸리는 일은 타임아웃 숫자만 계속 늘리지 않는다. 실시간 응답이 필요 없는 대량 작업은 요청 하나의 연결을 오래 붙드는 대신 비동기 실행과 상태 조회로 옮긴다.

폴백은 모델 이름이 아니라 출력 계약을 바꾸는 일이다

장애 때 다른 모델 ID로 갈아 끼운 뒤 HTTP 200이 왔다고 복구된 것은 아니다. 자유 텍스트, 구조화 JSON, 도구 호출은 공급사마다 응답 구조와 종료 상태가 다르다. 모델 A가 만든 tool_call을 모델 B가 같은 이름과 인수로 낼 것이라는 보장도 없다.

폴백 후보는 다음 다섯 조건을 통과해야 한다.

  1. 같은 입력에서 필요한 필드와 JSON 스키마를 지키는가
  2. 도구 이름·인수·결과 ID를 애플리케이션이 변환할 수 있는가
  3. 거절과 안전 정책의 차이가 업무 규칙을 깨지 않는가
  4. 데이터 처리 지역·보존 조건이 기존 계약 안에 들어오는가
  5. 첫 호출과 폴백 호출을 합친 지연·토큰·비용이 상한 안에 드는가

폴백은 반드시 다른 공급사일 필요도 없다. 마지막으로 승인한 결과 캐시, 사람이 검토한 템플릿, 기능 축소, 비동기 처리, 명확한 상태 안내가 더 안전한 대체 경로일 수 있다. 특히 결제나 권한 변경처럼 부수 효과가 큰 기능은 품질이 낮은 모델로 억지로 이어가기보다 실패를 닫아 두는 쪽이 낫다.

기능먼저 넣을 복구 경로공급사 간 폴백을 넣는 조건
고객에게 바로 보이는 검색·요약짧은 재시도 뒤 캐시 또는 기능 축소결과 계약과 지연 상한을 사전 시험했을 때
내부 문서 분류대기열로 이동해 나중에 재처리처리 마감이 있고 샘플 정확도를 통과했을 때
JSON을 저장하는 자동화스키마 실패로 격리양쪽 모델이 같은 업무 검증까지 통과할 때
메일·결제·DB 쓰기 도구사람 승인 또는 실패 종료동시 실행 차단·외부 상태 조회·권한 검사를 도구 실행부에 두었을 때

모델이 종료됐거나 별칭이 바뀐 경우는 장애 폴백과 별도의 변경 관리 문제다. 현재 모델 ID와 종료일은 OpenAI·Claude·Gemini API 지원 종료 일정에 정리돼 있다.

비용 제한은 공급사 설정과 서비스 내부 경계를 같이 둔다

공급사의 비용 상한은 마지막 방어선이지 요청 하나의 예산 관리가 아니다. OpenAI 비용 상한 문서는 알림과 강제 상한을 구분한다. 알림은 트래픽을 멈추지 않는다. 강제 상한은 429로 요청을 막지만 반영이 즉시 이뤄지지 않아 설정액을 조금 넘을 수 있다.

Gemini 결제 문서도 프로젝트 비용 상한을 실험 기능으로 표시하며 약 10분의 처리 지연 때문에 초과액이 생길 수 있다고 경고한다. Claude 문서 역시 사용자가 비용 상한을 티어 상한보다 낮춰 둘 수 있다고 안내한다.

그래서 서비스 코드에는 공급사 콘솔보다 좁은 경계를 둔다. 요청 전에 사용자·기능·테넌트의 남은 예산을 확인하고 최대 출력 토큰과 폴백 횟수를 제한한다. 완료 뒤에는 첫 공급사와 폴백 공급사의 사용량을 같은 논리 작업에 합산한다. 공급사 알림이 온 뒤 대응하는 구조만으로는 재시도 폭주를 제때 끊기 어렵다.

실제 청구 이상이 생겼을 때 공급사 화면과 우리 로그를 맞추는 방식은 Claude 251억 원 결제 요청에서 이메일·승인·최종 결제를 구분한 과정에서 볼 수 있다. 화면 숫자 하나만 보고 모델 호출이 실제로 그만큼 발생했다고 단정하면 안 된다는 사례다.

장애 로그에는 원문보다 판정 근거를 남긴다

프롬프트와 응답 전체를 쌓으면 디버깅은 쉬워 보인다. 대신 개인정보, 계약 문서, 내부 코드가 로그 시스템에 한 벌 더 생긴다. 기본 로그에는 원문을 빼고 다음 필드부터 남기는 편이 낫다.

{
  "job_id": "support-ticket:order-104:classification-v3",
  "provider": "anthropic",
  "model": "configured-model-id",
  "attempt": 2,
  "route": "primary",
  "latency_ms": 1840,
  "http_status": 429,
  "error_code": "enforced_spend_limit_reached",
  "request_id": "provider-request-id",
  "input_tokens": null,
  "output_tokens": null,
  "schema_valid": false,
  "tool_effect_committed": false
}

배포 버전, 기능명, 테넌트의 익명 식별자, 재시도 대기시간, 폴백 이유를 더하면 장애 전후를 비교하기 쉽다. OpenAI는 x-request-id, Claude는 request-id를 응답에서 제공한다. 지원팀에 특정 요청을 조회해 달라고 할 때도 이 값이 필요하다.

원문이 꼭 필요한 샘플은 별도 권한과 짧은 보존 기간을 둔다. OpenAI 데이터 제어 문서, Claude API 데이터 보존 문서, Gemini 로그 정책을 보면 같은 회사 안에서도 엔드포인트와 기능에 따라 저장 조건이 다르다. 공급사 정책을 우리 로그 보존 정책으로 그대로 복사해서는 안 된다.

운영자가 5분 안에 확인할 순서

  1. 우리 서비스의 오류율, 지연, 특정 모델·기능·테넌트 편중을 본다.
  2. 직전 배포와 SDK·모델 ID 변경 여부를 확인한다.
  3. HTTP 상태와 함께 error.code, details.error_code, Retry-After, 요청 ID를 읽는다.
  4. 공급사 콘솔에서 속도·토큰·비용·크레딧 한도를 확인한다.
  5. 그다음 상태 페이지를 본다. OpenAI 상태 페이지도 지표가 전체 티어와 모델을 합친 값이라 개별 고객 상태는 다를 수 있다고 적는다.
  6. 승인해 둔 경로만 연다. 캐시, 비동기 대기열, 검증한 모델, 사람 검토 중 하나도 없으면 명확하게 실패를 알린다.
  7. 복구 뒤 재시도 수, 폴백 비율, 추가 비용, 중복 부수 효과가 없었는지 확인한다.

‘에러가 나면 세 번 재시도’라는 한 줄 설정으로는 부족하다. 먼저 실패를 분류하고 논리 작업 하나의 시간·호출·비용 경계를 정한다. 재시도와 폴백은 그 경계 안에서만 연다.

이 글은 2026년 8월 25일 공개된 공식 문서와 SDK 설명을 대조해 작성했다. 유료 API로 장애를 주입하거나 실제 재시도 비용을 측정하지는 않았다. SDK 기본값, 오류 코드와 비용 상한 동작이 바뀌면 같은 URL에서 갱신한다. 공식 발표와 상태 페이지의 증거 강도를 구분하는 방법은 AI 루머와 공식 발표 구분 가이드에 정리돼 있다.

참고한 출처

공식 발표·문서·changelog 기반으로 작성했습니다. 전체 14개 중 공식 출처는 14개입니다.

함께 보면 좋은 글