블로그로 돌아가기

문제 해결가이드

AI API의 429 Too Many Requests: 백오프를 포함한 재시도

AI API의 429가 의미하는 것과 처리 방법: 지터를 포함한 지수 백오프, Retry-After, 에이전트 동시성, Python 및 JavaScript 코드 포함.

작성자: CheapTokens Team 게시됨 업데이트됨읽는 시간 6분

요청 루프 다이어그램: 429 응답, 지연이 증가하는 대기 단계, 재시도
이 페이지의 내용
  1. 429의 의미
  2. 지연 시간을 추측하기 전에 헤더를 읽으세요
  3. Python: 지터를 포함한 백오프
  4. JavaScript: 동일한 루프
  5. 에이전트가 429를 처리하는 방법
  6. CheapTokens에서 에이전트 루프 실행
  7. 그래도 실패하면

AI API에서 429 Too Many Requests은 너무 많이, 너무 빠르게 요청을 보냈거나 계정 한도에 도달해 요청이 거부되었다는 뜻입니다. 해결책은 지수 백오프와 지터를 사용해 기다렸다가 재시도하고, 응답에 Retry-After 헤더가 있으면 이를 사용하며, 에이전트가 병렬로 보내는 요청 수를 제한하는 것입니다. 실제로 지출 또는 잔액 한도인 429는 재시도하지 마세요. 기다린다고 해결되지 않습니다.

이 가이드는 OpenAI와 Anthropic의 공식 레이트 리밋 가이드를 기반으로 하며, 둘 다 2026년 10월 22일에 확인했습니다. 두 페이지 모두 최종 수정일을 표시하지 않습니다.

429의 의미#

OpenAI는 429를 레이트 리밋 초과로 인해 거부된 요청(트래픽이 너무 빠르게 증가할 때 코드 slow_down와 함께 rate_limit_error), 또는 하드 지출 한도에 도달했기 때문이라고 설명합니다. Anthropic도 429에 rate_limit_error을 사용합니다. 지출 한도 429에는 retry-after 헤더가 없으며 접근이 재개될 때까지 계속 실패합니다.

혼동하기 쉬운 인접한 두 가지가 있습니다. OpenAI의 503 server_is_overloaded과 Anthropic의 529 overloaded_error은 과부하 상태이며, 당신의 레이트 리밋이 아닙니다. 보통 동일한 재시도 루프로 처리하지만, 원인에 대해서는 다른 의미를 가집니다.

따라서 세 가지 경우가 있으며, 각각 다른 대응이 필요합니다:

응답 원인 해야 할 일
429 레이트 리밋 분당 요청 또는 토큰이 너무 많음 기다린 후 백오프로 재시도
429 지출 또는 잔액 한도 계정 한도 도달 재시도하지 말고 한도를 조정하거나 잔액을 충전
503 / 529 과부하 제공자 측이 바쁨 상한을 두고 백오프로 재시도

잔액이 소진되었다면 대신 "not enough tokens" 오류를 읽어보세요. 타임아웃과 연결 거부 같은 네트워크 수준 실패는 일반적인 연결 오류를 참고하세요.

지연 시간을 추측하기 전에 헤더를 읽으세요#

OpenAI는 레이트 리밋 응답에 다음 헤더를 문서화합니다: Retry-After, 그리고 x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests와 -tokens 변형. 다른 API는 이 중 일부만 보내거나 전혀 보내지 않을 수 있으므로, 클라이언트는 Retry-After이 있으면 사용하고 없으면 자체 스케줄로 대체하도록 작성하세요.

OpenAI의 재시도 지침을 요약하면:

  • Retry-After을 최소 대기 시간으로 취급하고 그 위에 작은 무작위 지연을 더하세요.
  • 없으면 지터를 포함한 지수 백오프를 사용하세요.
  • 시도 횟수와 총 재시도 시간 모두에 상한을 두세요.
  • SDK의 재시도 위에 자체 재시도를 겹치지 마세요.
  • 할당량 또는 결제 오류는 재시도하지 마세요.
  • 실패한 요청도 분당 한도에 포함되므로, 공격적인 루프는 상황을 악화시킵니다.
  • 스트리밍의 경우 이미 출력을 소비한 후에는 요청을 재생하지 마세요.

Python: 지터를 포함한 백오프#

공식 openai 패키지는 모든 OpenAI 호환 엔드포인트에서 작동합니다. SDK 자체 재시도(max_retries=0)를 꺼서 자체 재시도와 곱해지지 않도록 하세요. 모델 id는 CheapTokens 문서에서 가져옵니다. id는 제공자마다 다릅니다.

python
import os
import random
import time

from openai import OpenAI, APIStatusError

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
    max_retries=0,  # we retry ourselves
)

RETRYABLE = {429, 500, 502, 503, 504}


def chat_with_retry(model, messages, max_attempts=5, max_total=60.0):
    started = time.monotonic()
    for attempt in range(max_attempts):
        try:
            return client.chat.completions.create(model=model, messages=messages)
        except APIStatusError as err:
            last = attempt == max_attempts - 1
            if err.status_code not in RETRYABLE or last:
                raise
            header = err.response.headers.get("retry-after")
            if header and header.replace(".", "", 1).isdigit():
                delay = float(header) + random.uniform(0, 1)  # minimum wait + jitter
            else:
                delay = random.uniform(0, min(30.0, 2.0 ** attempt))  # full jitter
            if time.monotonic() - started + delay > max_total:
                raise
            time.sleep(delay)

헤더가 없을 때 지연의 상한은 1, 2, 4, 8초로 증가하며(최대 30), random.uniform은 그 아래에서 무작위 값을 선택하므로 많은 워커가 동시에 깨어나지 않습니다. Retry-After은 HTTP 날짜일 수도 있습니다. 위 코드는 숫자 형식을 처리하고 그렇지 않으면 백오프로 대체합니다.

JavaScript: 동일한 루프#

javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.cheaptokens.dev/v1",
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  maxRetries: 0, // we retry ourselves
});

const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function chatWithRetry(model, messages, maxAttempts = 5, maxTotalMs = 60000) {
  const started = Date.now();
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await client.chat.completions.create({ model, messages });
    } catch (err) {
      const last = attempt === maxAttempts - 1;
      if (!RETRYABLE.has(err.status) || last) throw err;
      const header = Number(err.headers?.get?.("retry-after"));
      const delayMs = Number.isFinite(header) && header > 0
        ? header * 1000 + Math.random() * 1000 // minimum wait + jitter
        : Math.random() * Math.min(30000, 2 ** attempt * 1000); // full jitter
      if (Date.now() - started + delayMs > maxTotalMs) throw err;
      await sleep(delayMs);
    }
  }
}

작동하면 레이트 리밋된 호출은 단순히 더 오래 걸린 후 정상 완료를 반환합니다. 마지막 시도 후에도 실패하면 원래 오류가 발생하므로 코드에서 로그를 남기거나 작업을 나중을 위해 큐에 넣을 수 있습니다.

에이전트가 429를 처리하는 방법#

에이전트 루프는 많은 짧은 호출을 보내며, 여러 에이전트나 도구 호출이 동시에 실행될 수 있습니다. 재시도만으로는 충분하지 않습니다. 소스에서 요청 속도를 낮추세요:

  1. 동시성에 상한을 두세요. 3–5개의 병렬 호출 세마포어가 합리적인 시작점입니다. 오류가 드물게 유지될 때만 늘리세요.
  2. 하나의 리미터를 공유하세요. 여러 워커가 같은 키를 사용하면 같은 한도를 공유하므로 워커당 하나의 재시도 루프로는 충분하지 않습니다. 프로세스에 하나의 큐 또는 하나의 세마포어를 사용하세요.
  3. 전역적으로 백오프하세요. 한 호출이 지연과 함께 429를 받으면 실패한 호출만이 아니라 전체 풀을 그 시간 동안 일시 중지하세요.
  4. 요청을 더 작게 유지하세요. 요청당 토큰이 적으면 분당 토큰 한도에 대한 압력이 줄어듭니다. 기록과 도구 출력을 줄이세요. 토큰이 어디로 가는지는 AI 에이전트 비용 추정 방법을 참고하세요.
  5. 재시도할 수 없는 오류에서는 중단하세요. 401, 400 또는 잔액 오류는 스스로 해결되지 않습니다. 빠르게 실패하고 보고하세요.
python
import asyncio

limit = asyncio.Semaphore(4)  # at most 4 requests in flight


async def run(job):
    async with limit:
        return await call_model(job)  # your async call with the retry loop above

CheapTokens에서 에이전트 루프 실행#

많은 짧은 호출이 있는 에이전트 루프의 경우 CheapTokens는 MiniMax-M2.7을 제공합니다: 하루 중 언제든 동일한 가격, 180K 컨텍스트, 답변당 최대 8,192 토큰, 모든 모델에 하나의 잔액. 출력 비용은 1M 토큰당 $0.15이며, 입력과 출력을 합산한 총액 기준으로 OpenRouter보다 저렴합니다. OpenAI 호환 엔드포인트 https://api.cheaptokens.dev/v1를 통해 작동합니다. 위 코드는 이미 이 엔드포인트를 가리키고 있습니다. CheapTokens 문서에서 모델 id를 설정하고 실행하세요.

500,000 입력, 100,000 출력 워크로드의 실시간 가격:

모델 비용 OpenRouter
GLM-5.3-Flash $0.014 $0.0282
DeepSeek-V4-Flash $0.0175 $0.0394
MiniMax-M2.7 $0.04 $0.27

총 입력 500,000개 및 출력 100,000개 토큰입니다. 100만 토큰당 가격은 현재 기준이며 당사 요금제에 따라 함께 변경됩니다. OpenRouter 열은 동일한 작업량에 대한 참고 가격을 보여줍니다.

모든 가격, 설정 문서를 참고하고, 키를 생성해 첫 요청을 보내세요.

그래도 실패하면#

  • 첫 요청에서 429. 응답 본문의 오류 유형과 메시지를 읽으세요. 지출 또는 계정 한도 429는 한도가 올라가거나 접근이 재개될 때까지 재시도에서 계속 실패하는 반면, 레이트 리밋 429는 일시 중지 후 해소됩니다.
  • 긴 루프에서 429가 계속 발생. 재시도를 겹치고 있을 수 있습니다: SDK 재시도 곱하기 자체 재시도. 예제처럼 SDK를 0으로 설정하세요.
  • 배치 시작 시 오류가 몰려 발생. 모든 워커를 한 번에 실행하지 말고 점진적으로 늘리세요. OpenAI는 너무 빠르게 증가하는 트래픽에 대해 slow_down을 보고합니다.
  • 스트리밍 호출이 중간에 실패. 출력을 소비한 후에는 무작정 재생하지 마세요. 전체 답변을 다시 시작할지 아니면 지금까지의 내용에서 계속할지 결정하세요.
  • 응답에 Retry-After 없음. 코드가 지터를 포함한 백오프로 대체하므로 변경이 필요하지 않습니다.