Anthropic API를 호출하다 보면 어느 순간 529 Overloaded 오류가 튀어나옵니다. 서버가 과부하 상태일 때 발생하는 rate limit 응답인데, 이걸 그냥 두면 스크립트가 중간에 멈춰버립니다. 처리 로직을 제대로 갖추지 않으면 배치 작업이나 자동화 파이프라인 전체가 흔들릴 수 있습니다.
이 글에서는 Anthropic API의 529 오류 특성을 짚고, Python requests 기반으로 지수 백오프(exponential backoff) retry 로직을 구성하는 방법을 단계별로 정리합니다.
529 오류가 발생하는 구조

HTTP 529는 표준 상태 코드가 아닙니다. Anthropic이 자체적으로 사용하는 커스텀 응답 코드로, 공식 문서에는 “API is temporarily overloaded” 상태로 정의되어 있습니다. 429(Too Many Requests)와 혼동하기 쉽지만 성격이 다릅니다.
| 코드 | 의미 | 원인 | 처리 방향 |
|---|---|---|---|
| 429 | Too Many Requests | 분당 요청 한도 초과 | 요청 빈도 줄이기 |
| 529 | Overloaded | 서버 일시 과부하 | 잠시 대기 후 재시도 |
529는 내 요청 횟수와 무관하게 Anthropic 서버 상태에 따라 발생합니다. 즉, 아무리 요청을 줄여도 서버 부하가 높으면 뜰 수 있습니다. 이 때문에 단순히 호출 빈도를 낮추는 것만으로는 해결되지 않고, 재시도 로직이 반드시 필요합니다.
지수 백오프란 무엇인가
지수 백오프(exponential backoff)는 실패할 때마다 대기 시간을 지수적으로 늘려가며 재시도하는 전략입니다. 예를 들어 첫 번째 실패 후 1초, 두 번째 실패 후 2초, 세 번째 실패 후 4초처럼 간격을 늘립니다.
여기에 jitter(무작위 편차)를 추가하면 다수의 클라이언트가 동시에 재시도하는 현상(thundering herd)을 막을 수 있습니다. Anthropic 공식 문서에서도 오류 처리 가이드에서 이 방식을 권장합니다.
| 재시도 횟수 | 기본 대기 시간 | jitter 적용 후 (예시) |
|---|---|---|
| 1회 | 1초 | 1.3초 |
| 2회 | 2초 | 2.7초 |
| 3회 | 4초 | 4.1초 |
| 4회 | 8초 | 8.9초 |
| 5회 | 16초 | 15.6초 |
지수 백오프 + jitter 재시도 흐름
Python requests로 구현하는 retry 로직
아래는 requests 라이브러리와 Python 표준 모듈만으로 구성한 기본 구조입니다. 외부 의존성을 최소화한 형태입니다.
import requests
import time
import random
def call_anthropic_with_retry(
url: str,
headers: dict,
payload: dict,
max_retries: int = 5,
base_delay: float = 1.0,
max_delay: float = 60.0
) -> requests.Response:
for attempt in range(max_retries):
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
return response
if response.status_code in (429, 529):
if attempt == max_retries - 1:
response.raise_for_status()
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1)
wait_time = delay + jitter
print(f"[{response.status_code}] {attempt + 1}회 실패 — {wait_time:.1f}초 대기 후 재시도")
time.sleep(wait_time)
continue
response.raise_for_status()
raise RuntimeError("최대 재시도 횟수를 초과했습니다.")
핵심 포인트는 세 가지입니다.
- 429와 529를 함께 처리합니다. 두 코드 모두 재시도 대상입니다.
- max_delay로 상한을 둡니다. 지수 증가가 무한정 커지지 않도록 60초로 cap을 겁니다.
- jitter는 10% 범위 내로 제한합니다. 과도한 무작위성은 오히려 예측을 어렵게 만듭니다.
tenacity 라이브러리를 쓰는 방법
tenacity는 Python retry 로직을 데코레이터 형태로 간결하게 작성할 수 있는 라이브러리입니다. 직접 반복문을 짜는 것보다 코드가 훨씬 줄어듭니다.
from tenacity import (
retry,
stop_after_attempt,
wait_exponential,
retry_if_exception_type,
before_sleep_log
)
import requests
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class AnthropicOverloadError(Exception):
pass
@retry(
retry=retry_if_exception_type(AnthropicOverloadError),
wait=wait_exponential(multiplier=1, min=1, max=60),
stop=stop_after_attempt(5),
before_sleep=before_sleep_log(logger, logging.INFO)
)
def call_anthropic(url, headers, payload):
response = requests.post(url, headers=headers, json=payload)
if response.status_code in (429, 529):
raise AnthropicOverloadError(f"서버 과부하: {response.status_code}")
response.raise_for_status()
return response
wait_exponential이 jitter를 자동으로 포함하므로 별도로 구현할 필요가 없습니다. before_sleep_log를 붙이면 재시도 시점마다 로그가 남아 디버깅에 유리합니다.
tenacity retry 데코레이터 적용 예시
실전 적용 시 주의할 점
구현 자체보다 운영 환경에서 놓치기 쉬운 부분이 있습니다.
1. Retry-After 헤더 확인
429 응답에는 종종 Retry-After 헤더가 포함됩니다. 이 값이 있으면 지수 백오프 계산값보다 이 헤더 값을 우선합니다. 무시하면 서버 정책을 위반할 수 있습니다.
retry_after = response.headers.get("Retry-After")
if retry_after:
time.sleep(float(retry_after))
2. 멱등성(idempotency) 확인
재시도할 요청이 부작용 없이 반복 가능한지 확인해야 합니다. Anthropic Messages API는 POST 요청이지만 동일 요청을 여러 번 보내도 결과가 중복 저장되지 않으므로 기본적으로 재시도에 안전합니다.
3. 재시도 횟수와 총 대기 시간
max_retries=5, base_delay=1.0, max_delay=60.0 기준으로 최악의 경우 총 대기 시간은 약 1 + 2 + 4 + 8 + 16 = 31초입니다. 실시간 응답이 필요한 서비스라면 max_retries를 3으로 줄이고 별도 fallback 처리를 구성하는 편이 낫습니다.
4. 스트리밍 응답에서의 처리
Anthropic API의 스트리밍 모드(stream=True)에서는 응답 도중 529가 발생할 수 있습니다. 이 경우 이미 수신한 청크는 버리고 처음부터 재시도해야 합니다. 부분 응답을 이어붙이는 방식은 지원되지 않습니다.
자주 묻는 질문
Q. 529와 500 오류도 같이 retry 해야 하나요?
500 Internal Server Error는 서버 측 버그일 가능성이 있어 무조건 재시도하면 오히려 문제를 키울 수 있습니다. 529는 과부하로 인한 일시적 거부이므로 재시도가 적합하고, 500은 1~2회로 제한하거나 별도 알림 처리를 권장합니다.
Q. Anthropic 공식 Python SDK를 쓰면 retry가 자동으로 되나요?
anthropic 공식 SDK는 내부적으로 httpx 기반 retry 로직을 포함하고 있습니다. max_retries 파라미터를 클라이언트 초기화 시 설정할 수 있습니다. 단, SDK가 제공하는 기본 동작을 그대로 쓸지, 커스텀 로직을 추가할지는 요구 사항에 따라 결정합니다.
Q. 배치 작업에서 529가 자주 발생하면 어떻게 해야 하나요?
Anthropic은 대규모 배치 처리를 위한 Message Batches API를 별도로 제공합니다. 실시간 처리가 아닌 경우 이 API를 활용하면 529를 구조적으로 줄일 수 있습니다.
529 오류는 피할 수 없는 상황이 있습니다. retry 로직을 갖추는 것은 선택이 아니라 안정적인 API 연동의 기본 요건입니다. 직접 구현이 부담스럽다면 tenacity부터 적용하는 것이 가장 빠른 방법입니다.
썸네일: Hitesh Choudhary on Unsplash