AI API 속도 제한(Rate Limits) 문제를 해결하고 정신 건강을 지킨 방법
요약
AI API 호출 시 발생하는 Rate Limit(429) 및 일시적 에러 문제를 해결하기 위한 견고한 재시도 전략을 소개합니다. 단순 sleep이나 수동 루프 대신 Python의 tenacity 라이브러리를 활용하여 지수 백오프와 지터를 적용하는 방법을 다룹니다.
핵심 포인트
- 단순 sleep 방식은 효율성이 낮고 연속 실패 대응이 어려움
- 지터(Jitter)가 없는 백오프는 클라이언트 간 동시 요청 폭주를 유발함
- tenacity 라이브러리를 통해 지수 백오프와 중단 조건을 우아하게 구현 가능
- 에러 유형별로 차별화된 재시도 정책을 세우는 것이 중요함
노트북을 창밖으로 던져버리고 싶었던 첫 번째 순간
새벽 2시였습니다. 사이드 프로젝트에 AI 텍스트 생성 기능을 막 연결한 참이었죠. 데모는 아주 훌륭하게 작동했습니다. 세 번의 프롬프트(Prompt)를 연속으로 넣었을 때 모두 완벽한 응답이 돌아왔습니다. 저는 미소를 지으며 노트북을 닫고 잠자리에 들었습니다.
다음 날, 블로그 포스트 생성기를 위해 100개의 프롬프트 배치(Batch) 작업을 실행했습니다. 그런데 30초도 안 되어 모든 것이 망가졌습니다. 콘솔에는 속도 제한(Rate limit) 에러가 쏟아졌습니다. 어떤 호출은 429 에러를 반환했고, 어떤 것은 503 에러를 반환했습니다. 몇몇은 그냥 영원히 멈춰버렸습니다. 전체 파이프라인(Pipeline)이 중단되었고, 저는 이후 3시간 동안 수동으로 요청을 재시도하고, 에러 핸들러(Error handler)를 다시 작성하며, 제 인생의 선택에 대해 회의감을 느껴야 했습니다.
익숙한 상황인가요? 만약 여러분이 OpenAI, Anthropic, 혹은 더 작은 규모의 제공업체라도 어떤 AI API를 통합해 본 적이 있다면, 아마 이 벽에 부딪혀 본 적이 있을 것입니다. API들은 놀랍지만, 결코 완벽하지는 않습니다. 속도 제한(Rate limits)이 있고, 일시적인 에러(Transient errors)가 발생하며, 타임아웃(Timeout) 특이사항도 존재합니다.
오늘 저는 마침내 저를 구원해 준 접근 방식을 공유하고자 합니다. 거의 모든 AI API에서 작동하는 탄력적이고 범용적인 재시도 및 백오프(Retry-and-backoff) 패턴입니다. 마법 같은 것은 없습니다. 그저 견고한 코드일 뿐입니다.
시도했지만 실패했던 방법들
1. 에러 발생 후 단순 time.sleep() 사용
import requests
def call_ai_api(prompt):
...
이 방법은... 가끔 작동했습니다. 하지만 여러 번 연속되는 실패를 처리하지 못했고, API가 빠르게 복구되었을 때도 무조건 10초를 기다려야 하므로 시간을 낭비하게 만들었습니다.
2. for 루프를 이용한 수동 지수 백오프(Exponential backoff)
def call_with_backoff(prompt, max_retries=5):
for attempt in range(max_retries):
try:
...
더 나았지만, 여전히 취약했습니다. 서로 다른 에러 유형(예: 401 vs 429)을 구분하지 못했고, 지터(Jitter)를 추가하지 않았습니다. 그래서 여러 클라이언트가 동시에 재시도를 하면 모두가 동시에 API를 몰아치게 됩니다. 또한, 타임아웃을 올바르게 처리하려면 추가적인 로직이 필요했습니다.
저는 바퀴를 다시 발명하지 않고도 프로덕션급(Production-grade)으로 사용할 수 있는 무언가가 필요했습니다.
결국 성공한 방법: tenacity 라이브러리
저는 우아하게 재시도(retries)를 처리해 주는 Python 라이브러리인 tenacity를 발견했습니다. 이 라이브러리를 사용하면 지수 백오프 (exponential backoff), 지터 (jitter), 중단 조건 (stop conditions), 그리고 커스텀 재시도 콜백 (custom retry callbacks)을 포함한 재시도 정책을 최소한의 코드로 정의할 수 있습니다.
이제 제가 모든 AI API 호출에 사용하는 패턴은 다음과 같습니다:
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, before_sleep_log
import logging
...
이 방법이 효과적인 이유:
- 지터를 포함한 지수 백오프 (Exponential backoff with jitter) (tenacity는 기본적으로 내부에서 지터를 추가함)는 재시도 간격을 분산시켜, 천둥 치는 들소 떼 (thundering herd) 문제를 방지합니다.
- 특정 에러에 대해서만 재시도 (429 또는 5xx와 같은 에러). 영구적인 실패 (예: 401 잘못된 키)는 즉시 실패 처리합니다.
- **재시도 사이의 로깅 (Logging between retries)**을 통해 불필요한 스팸 없이 현재 상황을 파악할 수 있습니다.
- 요청에 대한 타임아웃 (Timeout on the request) (30초)을 설정하여 응답이 없는 엔드포인트에서 무한정 대기하는 것을 방지합니다.
- 설정 가능한 최대 시도 횟수 (Configurable max attempts) – 보통 5회면 충분합니다. 5회 시도 후에도 실패한다면 근본적으로 무언가 잘못된 것입니다.
또한, 서로 다른 API를 처리하기 위해 컨텍스트 매니저 (context manager)를 추가했습니다:
from contextlib import contextmanager
@contextmanager
...
네, 위의 URL은 실제 예시입니다. 저는 셀프 호스팅된 AI 엔드포인트(self-hosted AI endpoint)를 위해 유사한 설정을 사용한 적이 있습니다. 하지만 이 기술은 어떤 제공업체(provider)에게도 적용 가능합니다.
교훈 (Lessons Learned)
- 지터 (jitter)를 무시하지 마세요. 몇 밀리초(ms)의 무작위성만으로도 동기화된 재시도(synchronized retries)로 인해 백엔드 전체가 무너지는 것을 방지할 수 있습니다.
- 재시도와 관련된 모든 것을 로그로 남기세요. 새벽 3시에 조용한 실패(silent failure)를 디버깅할 때 스스로에게 고마워하게 될 것입니다.
- 재시도 가능한 오류와 불가능한 오류를 분리하세요. 401 또는 403 오류는 인증 정보가 잘못되었음을 의미합니다. 이 경우 재시도는 무의미하며 계정이 차단될 수도 있습니다.
- 부하가 심한 시나리오를 위해 서킷 브레이커 (circuit breaker)를 고려하세요. 배치 처리 (batch processing)의 경우
tenacity의 재시도 기능으로 충분하지만, 수백만 건의 호출을 수행해야 한다면pybreaker나 이와 유사한 도구를 살펴보세요. - 모의 서버 (mock server)로 테스트하세요. 프로덕션 환경에 적용하기 전에 responses나 moto를 사용하여 429 오류를 시뮬레이션하세요.
다음에 다르게 할 점
만약 처음부터 다시 시작한다면, 코드 곳곳에 try/except 블록을 흩뿌리는 대신 첫날부터 이 재시도 로직을 커스텀 클라이언트 클래스 (custom client class)에 구축했을 것입니다. 또한, 동시 호출을 위해 tenacity.async와 asyncio를 사용했을 것입니다. 하지만 그건 다른 글에서 다루겠습니다.
한 가지 더: 속도 제한 (rate limiting)은 단순히 재시도에 관한 것만이 아닙니다. API 제한 범위 내에 머물기 위해 스스로의 요청을 조절 (throttle)하는 것도 필요합니다. 저는 결국 ratelimit 라이브러리를 사용하여 토큰 버킷 (token bucket) 알고리즘을 추가했고, 분당 예를 들어 60개 이상의 요청을 보내지 않도록 설정했습니다. 속도 제한과 재시도 로직을 결합하는 것이 진정한 비법 (secret sauce)입니다.
여러분의 차례
다음에 어떤 AI API를 통합하더라도, 재시도 로직을 처음부터 직접 작성하지 마세요. 검증된 라이브러리를 사용하고, 재시도 가능한 오류를 정의하며, 지터 (jitter)를 포함한 지수 백오프 (exponential backoff)를 추가하고, 재시도 과정을 로그로 남기세요. 미래의 당신이 고마워할 것입니다.
불안정한 API를 처리하는 여러분만의 패턴은 무엇인가요? 더 나은 접근 방식을 찾으셨나요? 댓글로 알려주세요. 저는 항상 이 설정을 개선할 방법을 찾고 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기