레이트 리밋(속도 제한)
레이트 리밋(속도 제한)
레이트 리밋은 API가 특정 시간 동안 사용자나 클라이언트가 서비스를 호출할 수 있는 횟수를 제한하는 규칙이에요. 왜 이런 걸 둘까요? API의 남용을 막고, 모든 사용자가 공정하게 접근하고, 인프라에 가해지는 부하를 관리하기 위해서예요. 이 문서를 처음부터 끝까지 읽어 보면 OpenAI의 레이트 리밋 체계가 어떻게 돌아가는지 이해하고, 흔한 문제를 처리하는 코드 예시와 해결책까지 얻을 수 있습니다.
출처: 공식문서
왜 레이트 리밋이 있을까?
레이트 리밋은 API에서 흔한 관행이고, 뒷받침하는 이유가 몇 가지 있어요.
- 남용이나 오용으로부터 보호한다. 악의적인 사용자가 API를 쏟아부어 서비스를 마비시키거나 장애를 일으키려 할 수 있어요. 요청 수를 제한하면 이런 활동을 막을 수 있죠.
- 모두에게 공정한 접근을 보장한다. 한 사람이나 조직이 과도하게 요청하면 다른 모두에게까지 API가 느려질 수 있어요. 단일 사용자의 요청 수를 조절하면 최대한 많은 사람이 지연 없이 API를 쓸 수 있게 됩니다.
- 인프라의 전체 부하를 관리한다. API 요청이 급증하면 서버에 부담이 가고 성능 문제가 생길 수 있어요. 레이트 리밋으로 모두에게 안정적이고 일관된 경험을 유지할 수 있죠.
레이트 리밋은 어떻게 동작할까?
레이트 리밋은 RPM(분당 요청), RPD(하루 요청), TPM(분당 토큰), TPD(하루 토큰), IPM(분당 이미지) 같은 지표를 사용해요. 일부 스트리밍 오디오 모델은 분당 오디오 분(minutes)을 쓰기도 합니다. 어떤 지표가 먼저 한도에 닿느냐에 따라 리밋이 걸리는데, 예를 들어 RPM이 20이라면 100토큰짜리 요청 20개만 보내도 한도를 채울 수 있어요. TPM 한도가 150k라고 해도 그 20개 요청 안에 150k 토큰을 보내지 않았다면 말이죠.
Batch API의 큐 한도는 주어진 모델에 큐에 쌓인 입력 토큰 총합으로 계산되고, 보류 중인 배치 작업의 토큰도 큐 한도에 집계됩니다. 배치 작업이 완료되면 그 토큰은 더 이상 그 모델의 한도에 포함되지 않아요.
기억해 둘 점이 몇 가지 있어요:
- 레이트 리밋은 사용자 레벨이 아니라 조직 레벨과 프로젝트 레벨에서 정의됩니다.
- 레이트 리밋은 사용하는 모델에 따라 달라져요.
- GPT-5.5 같은 긴 컨텍스트 모델은 긴 컨텍스트 요청에 대해 별도의 레이트 리밋이 있어요. 개발자 콘솔에서 확인할 수 있죠.
- OpenAI는 조직마다 승인된 월별 사용 한도를 정해요. 조직이나 프로젝트에 직접 설정하는 지출 한도와는 별개의 개념입니다.
- 일부 모델 패밀리는 레이트 리밋을 공유해요. 조직 한도 페이지의 "shared limit"에 나열된 모델은 서로 한도를 공유하므로, 공유 TPM이 3.5M이라면 그 목록의 어떤 모델 호출이든 그 3.5M에 집계됩니다.
- 벡터 스토어 수집도 벡터 스토어 ID별로 레이트 리밋이 걸려요.
/vector_stores/{vector_store_id}/files와/vector_stores/{vector_store_id}/file_batches는 벡터 스토어마다 분당 300요청을 함께 공유하고, 대량 수집에는 후자를 권장합니다.
사용 티어(Usage tiers)
자기 조직의 레이트·사용 한도는 계정 설정의 limits 섹션에서 볼 수 있어요. API 지출이 늘어나면 자동으로 다음 사용 티어로 승급되면서 대부분의 모델에서 레이트 리밋이 올라가는 게 보통입니다.
| 티어 | 자격 | 사용 한도 |
|---|---|---|
| Free | 허용 지리 내 사용자여야 함 | $100 / 월 |
| Tier 1 | $5 결제 | $100 / 월 |
| Tier 2 | $50 결제 | $500 / 월 |
| Tier 3 | $100 결제 | $1,000 / 월 |
| Tier 4 | $250 결제 | $5,000 / 월 |
| Tier 5 | $1,000 결제 | $200,000 / 월 |
모델별 레이트 리밋 개요는 models 페이지에서 확인할 수 있어요.
헤더의 레이트 리밋 정보
계정 페이지에서 레이트 리밋을 보는 것 외에도, HTTP 응답 헤더에서 남은 요청 수·토큰·기타 메타데이터를 확인할 수 있어요. 응답에는 이런 헤더 필드가 포함될 수 있습니다.
| 필드 | 예시 값 | 설명 |
|---|---|---|
| Retry-After | 56 | 일시적 레이트 리밋 오류 후 재시도 전에 기다려야 할 최소 초. 있을 때만 의미. |
| x-ratelimit-limit-requests | 60 | 레이트 리밋이 소진되기 전에 허용되는 최대 요청 수 |
| x-ratelimit-limit-tokens | 150000 | 레이트 리밋이 소진되기 전에 허용되는 최대 토큰 수 |
| x-ratelimit-remaining-requests | 59 | 소진 전에 남은 허용 요청 수 |
| x-ratelimit-remaining-tokens | 149984 | 소진 전에 남은 허용 토큰 수 |
| x-ratelimit-reset-requests | 1s | 요청 기준 레이트 리밋이 초기 상태로 재설정될 때까지의 시간 |
| x-ratelimit-reset-tokens | 6m0s | 토큰 기준 레이트 리밋이 초기 상태로 재설정될 때까지의 시간 |
| x-ratelimit-limit-project-tokens | 60000 | 프로젝트의 토큰 한도 |
| x-ratelimit-remaining-project-tokens | 57000 | 프로젝트 범위 토큰 리밋 소진 전에 남은 허용 토큰 수 |
| x-ratelimit-reset-project-tokens | 3s | 프로젝트 범위 토큰 리밋이 재설정될 때까지의 시간 |
프로젝트 토큰 헤더는 프로젝트 범위 토큰 한도가 적용될 때 나타날 수 있어요. Retry-After는 일시적 레이트 리밋으로 인한 429와 일시적 모델 과부하로 인한 503 응답에 나타날 수 있어요. 쿼터나 과금처럼 사용자의 조치가 필요한 오류는 재시도로 해결된다는 의미는 아니니 주의하세요.
파인튜닝 레이트 리밋
조직의 파인튜닝 레이트 리밋은 대시보드에서 볼 수 있고, API로도 조회할 수 있어요:
curl https://api.openai.com/v1/fine_tuning/model_limits \
-H "Authorization: Bearer ***"
오류 완화(Error mitigation)
급격한 트래픽 증가와 모델 과부하 처리
요청 속도가 너무 빨리 올라가면 API가 slow_down을, 요청한 모델이 일시적으로 과부하 상태면 server_is_overloaded를 반환할 수 있어요. HTTP 상태와 error.code를 확인해 두 상황을 구분하면 됩니다.
| HTTP 상태 | 오류 유형 | 오류 코드 | 의미 | 대처 |
|---|---|---|---|---|
429 |
rate_limit_error |
slow_down |
요청 속도가 너무 빨리 올라감 | Retry-After가 있으면 따르고, 요청 속도를 낮춘 뒤 점진적으로 다시 올림 |
503 |
service_unavailable_error |
server_is_overloaded |
요청한 모델이 일시적으로 과부하 | Retry-After가 있으면 따르고 재시도. 지속되면 재시도 간격을 늘림 |
Retry-After가 없으면 재시도 간격을 늘리고 작은 무작위 지연을 더하세요. slow_down 오류는 트래픽이 분당 요청·분당 토큰 한도 내에 있더라도 발생할 수 있어요. 이 오류는 한도를 소진했는지가 아니라 트래픽이 얼마나 빨리 증가했는지를 반영합니다.
경험상, 트래픽이 분당 100만 입력 토큰(TPM)에 도달하면 15분마다 50% 이하로만 늘리는 게 좋아요. 정확한 램프 속도 한도 적용 지점은 모델과 트래픽 상황에 따라 달라질 수 있어요.
종량제 트래픽이 자주 램프 속도 한도에 부딪히는 엔터프라이즈 고객이라면 적격 모델에서 더 예측 가능한 용량을 제공하는 Scale Tier를 고려해 볼 수 있어요. GPT-5.6 이후 모델은 Reserved Tier 문서를 참고하세요. 용량 티어가 slow_down 응답 처리 방식을 바꾸지는 않아요. Retry-After가 있으면 따르고, 트래픽을 줄이고 점진적으로 재개하면 됩니다.
기존 오류 핸들러 업데이트
이전에 스로틀링·과부하 응답을 처리한 애플리케이션이라면 HTTP 상태와 error.code를 함께 확인하세요.
- 이전에 두 상황 모두에서
503+slow_down코드를 반환하던 엔드포인트는, 이제 급격한 트래픽 증가가429+slow_down으로 반환돼요. 모델 과부하는 여전히503이지만server_is_overloaded를 사용합니다. - 비디오 요청이 작업 생성 전에 거부되던 경우는 이전에
429+invalid_request_error타입 +rate_limit_exceeded코드를 반환했어요. 이제 급격한 트래픽 증가는429+rate_limit_error+slow_down, 모델 과부하는503+service_unavailable_error+server_is_overloaded로 반환됩니다. 비디오 작업 상태에 보고되는 오류는 별개의 경우예요.
SDK 오류 핸들러에서는 429와 503을 모두 처리하세요. 예를 들어 Python·TypeScript·Ruby는 429에 RateLimitError, 503에 InternalServerError를 사용하고, Java는 RateLimitException·InternalServerException을 사용해요. 애플리케이션이 여전히 받을 수 있는 이전 응답 코드에 대한 지원도 유지하세요. 다른 오류도 같은 HTTP 상태를 쓸 수 있으니, 복구 동작을 고르기 전에 오류 본문을 확인해야 합니다.
스트리밍 요청에서는 이런 HTTP 오류 응답이 스트림이 시작되기 전에 적용돼요. 스트리밍이 시작된 뒤의 오류는 스트림 이벤트로 도착할 수 있으니, 출력을 소비한 뒤 요청을 자동으로 다시 재생하지 마세요.
완화를 위해 어떤 조치를 취할 수 있을까?
OpenAI Cookbook에는 레이트 리밋 오류를 피하는 방법을 설명하는 Python 노트북과, 배치 처리 중 레이트 리밋 이하를 유지하는 예시 Python 스크립트가 있어요. 프로그램 접근·대량 처리 기능·자동 소셜 미디어 게시를 제공할 때는 주의하고, 신뢰하는 고객에게만 켜는 걸 고려하세요. 자동화·대량 남용으로부터 보호하려면 개별 사용자에게 일정 기간(일·주·월)의 사용 한도를 설정하고, 한도를 넘은 사용자에게는 상한(하드 캡)이나 수동 검토 절차를 만들어 두는 게 좋습니다.
지수 백오프로 재시도하기
요청이 일시적 레이트 리밋을 초과하면 API가 429 오류를 반환해요. 응답에는 몇 초를 기다린 뒤 다시 시도할지 알려주는 Retry-After 헤더가 포함될 수 있고, 이 값은 최소값으로 취급해야 해요. 적어도 그만큼 기다리고 약간의 무작위 지연을 더해서 여러 클라이언트가 동시에 재시도하지 않게 해야 합니다.
각 공식 OpenAI SDK는 자체 재시도 설정에 따라 적격한 429·503 응답을 자동으로 재시도해요. 특히 긴 지연의 Retry-After 처리는 SDK 버전과 설정에 따라 다르므로, 설치 버전의 재시도 동작을 확인하고 모든 서버 지연이 지원된다고 가정하지 마세요.
유효한 서버 지연이 지원되거나 설정한 최대 재시도 지연을 초과하면, 더 빨리 재시도하기보다 재시도를 멈추고 요청을 미뤄 두세요. SDK는 한도 이상의 지연을 거부할 때 원래 HTTP 오류를 반환할 수 있어요. 취소·타임아웃 오류는 별도로 처리하세요. 취소된 요청이나 만료된 데드라인은 그 HTTP 오류 없이 재시도를 멈출 수 있으니 주의해야 해요. 개별 시도의 타임아웃이 전체 작업의 데드라인인 것은 아닙니다.
직접 HTTP 클라이언트를 쓴다면 헤더에 유효한 값이 있을 때 Retry-After를 따르세요. 없거나 유효하지 않으면 지터가 있는 지수 백오프로 폴백하되, 시도 횟수와 재시도에 쓰는 총 시간을 모두 제한하세요. 애플리케이션에서 재시도를 관리한다면 SDK 재시도를 끄거나 이 한도에 포함시켜 중첩 재시도 루프가 요청을 곱절로 만들지 않게 해야 합니다. 쿼터·과금 등 사용자 조치가 필요한 오류는 재시도하지 마세요.
지수 백오프란 실패한 요청 뒤에 잠시 기다렸다가, 재시도가 실패할 때마다 지연을 늘려 가는 방식이에요. 요청이 성공하거나 설정한 재시도 한도에 이를 때까지 이어집니다. 이 방식에는 여러 이점이 있어요.
- 자동 재시도로 크래시나 데이터 유실 없이 레이트 리밋 오류에서 복구할 수 있다.
- 지수 백오프로 첫 재시도는 빠르게 시도하면서도, 계속 실패하면 더 긴 지연을 활용할 수 있다.
- 무작위 지터를 더하면 재시도가 동시에 몰리지 않는다.
실패한 요청도 분당 한도에 집계되므로, 같은 요청을 계속 재전송하는 것은 통하지 않아요. 아래 Python 예시들은 폴백 백오프를 보여줘요. Retry-After는 검사하지 않으니, 사용 전에 유효한 서버 힌트를 처리해서 래퍼가 요청보다 빨리 재시도하지 않게 하고, SDK 재시도를 끄거나 애플리케이션 재시도 한도에 반영하세요.
예시 1: Tenacity 라이브러리 사용
Tenacity는 Apache 2.0 라이선스의 범용 재시도용 Python 라이브러리예요. 지수 백오프를 추가하려면 tenacity.retry 데코레이터를 쓰면 되고, 아래 예시는 tenacity.wait_random_exponential 함수로 요청에 무작위 지수 백오프를 더합니다.
from openai import OpenAI
from tenacity import (
retry,
stop_after_attempt,
wait_random_exponential,
) # for exponential backoff
client = OpenAI()
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
def completion_with_backoff(**kwargs):
return client.completions.create(**kwargs)
completion_with_backoff(
model="gpt-3.5-turbo-instruct",
prompt="Once upon a time,",
)
Tenacity는 서드파티 도구이며 OpenAI가 그 신뢰성이나 보안을 보장하지 않아요.
예시 2: backoff 라이브러리 사용
백오프와 재시도를 위한 함수 데코레이터를 제공하는 또 다른 Python 라이브러리로 backoff가 있어요.
import backoff
import openai
from openai import OpenAI
client = OpenAI()
@backoff.on_exception(backoff.expo, openai.RateLimitError)
def completions_with_backoff(**kwargs):
return client.completions.create(**kwargs)
completions_with_backoff(
model="gpt-3.5-turbo-instruct",
prompt="Once upon a time,",
)
Tenacity처럼 backoff도 서드파티 도구이며 OpenAI가 신뢰성이나 보안을 보장하지 않습니다.
예시 3: 수동 백오프 구현
서드파티 라이브러리를 쓰고 싶지 않다면 다음 예시를 따라 직접 백오프 로직을 구현할 수 있어요.
# imports
import random
import time
import openai
from openai import OpenAI
client = OpenAI()
# define a retry decorator
def retry_with_exponential_backoff(
func,
initial_delay: float = 1,
exponential_base: float = 2,
jitter: bool = True,
max_retries: int = 10,
errors: tuple = (openai.RateLimitError,),
):
"""Retry a function with exponential backoff."""
def wrapper(*args, **kwargs):
# Initialize variables
num_retries = 0
delay = initial_delay
# Loop until a successful response or max_retries is hit or an exception is raised
while True:
try:
return func(*args, **kwargs)
# Retry on specific errors
except errors:
# Increment retries
num_retries += 1
# Check if max retries has been reached
if num_retries > max_retries:
raise Exception(
f"Maximum number of retries ({max_retries}) exceeded."
)
# Increment the delay
delay *= exponential_base * (1 + jitter * random.random())
# Sleep for the delay
time.sleep(delay)
# Raise exceptions for any errors not specified
except Exception:
raise
return wrapper
@retry_with_exponential_backoff
def completions_with_backoff(**kwargs):
return client.completions.create(**kwargs)
이 역시 OpenAI가 보안이나 효율을 보장하지는 않지만, 직접 구현을 시작하기 좋은 출발점이 될 수 있어요.
max_tokens를 완성 크기에 맞게 줄이기
레이트 리밋은 max_tokens와 요청의 문자 수로 추정한 토큰 수 중 더 큰 값으로 계산돼요. max_tokens 값을 예상 응답 크기에 최대한 가깝게 설정해 보세요.
요청 배치 처리
즉시 응답이 필요 없는 사용 사례라면 Batch API를 쓰면 동기 요청 레이트 리밋에 영향을 주지 않고 대량 요청 컬렉션을 더 쉽게 제출·실행할 수 있어요. 동기 응답이 필요한 경우엔 OpenAI API에 분당 요청과 분당 토큰이 별도의 한도로 있어요. 분당 요청 한도에 부딪혔지만 분당 토큰 용량은 남았다면, 각 요청에 작업을 여러 개 묶어(batching) 처리량을 높일 수 있어요. 이러면 특히 작은 모델에서 분당 더 많은 토큰을 처리할 수 있습니다.
프롬프트 묶음 전송은 일반 API 호출과 완전히 같고, prompt 파라미터에 단일 문자열 대신 문자열 리스트를 넘기기만 하면 돼요. 자세한 내용은 Batch API 가이드에서 확인하세요.
더 알아보기 (Learn more)
- Batch API: 대량 요청을 오프라인으로 처리하고 별도 레이트 리밋 풀 활용
- 모델 문서: 모델별 레이트 리밋 개요
- Production best practices: 조직·프로젝트 레벨 리밋 이해