LLM Rate Limit Handling: Retries, Backoff, and Queueing at Scale
Quick answer
Quick answer: LLM providers limit both requests and tokens per minute, and exceeding them returns rate limit errors. Retry only retryable errors, use...
Quick answer: LLM providers limit both requests and tokens per minute, and exceeding them returns rate limit errors. Retry only retryable errors, use exponential backoff with jitter, honor any retry hint the provider sends, throttle on your side before you hit limits, and put non-urgent work in a queue. Add a fallback so one saturated model does not take your feature down.
What Limits Do You Actually Hit?
Providers usually enforce several limits at once: requests per minute, tokens per minute, and sometimes daily or concurrent usage caps. Token limits are easy to miss, because a few large prompts can exhaust capacity while the request count stays low. Limits also differ by model and account tier, so one setting rarely fits every call.
When you exceed a limit, the API typically responds with a rate limit status and may include headers describing remaining capacity or how long to wait. The exact fields differ between providers, so read their documentation. Use these hints instead of guessing, because they tell you the real reset time.
Limits also apply per organization or project, not per instance. If several services share one key, a batch job in one of them can starve interactive traffic in another. Separate keys or projects for background and user-facing workloads, where your provider allows it, so a noisy job cannot cause an outage elsewhere.
Not every error deserves a retry. Rate limits and temporary server errors are worth retrying, but invalid requests, authentication failures, and context length errors will fail every time. Retrying them wastes capacity and delays the real error from reaching your logs.
How Should You Retry?
Use exponential backoff: wait a short time after the first failure, then double it with each attempt up to a maximum. Add random jitter, so many clients that failed together do not retry in lockstep and create a second wave. Cap total attempts and total elapsed time, and surface a clear error when the budget is exhausted.
def call_with_backoff(request, max_attempts=5, base=0.5, cap=30):
for attempt in range(max_attempts):
resp = send(request)
if resp.ok:
return resp
if not is_retryable(resp.status):
raise ApiError(resp)
delay = resp.retry_after or min(cap, base * 2 ** attempt)
sleep(delay * random.uniform(0.5, 1.0))
raise RateLimited("retry budget exhausted")
Make requests safe to retry. Generation calls are usually harmless to repeat, but steps with side effects, such as a tool call that sends an email, need idempotency keys or deduplication so a retry cannot perform the action twice. Also set per-request timeouts so a stuck call does not hold capacity indefinitely.
Be careful with retry amplification. If each layer of your stack retries three times, one failure can become nine requests, and the extra load keeps the rate limit in place. Retry in one layer only, and pass a deadline downward so nested calls stop when the user has already given up.
How Do You Avoid Hitting the Limit at Scale?
Retries treat the symptom. At scale, control the demand before it reaches the provider:
- Estimate tokens per request and throttle with a shared limiter that tracks usage across all your instances.
- Cap concurrency per model so bursts do not overwhelm the quota.
- Queue non-interactive work, such as summaries and batch processing, and process it at a steady rate.
- Prioritize interactive user requests over background jobs when capacity is tight.
- Use batch interfaces for large offline workloads where the provider offers them.
Add resilience for the cases where limits still bite. A circuit breaker stops sending traffic to a struggling model for a short period, and a fallback routes requests to a secondary model or a degraded response. Track rate limit errors, queue depth, and wait times as metrics, and request higher limits from the provider based on measured need.
Frequently Asked Questions
Should I retry every failed LLM request?
No. Retry rate limits and temporary server errors. Do not retry invalid requests, authentication failures, or context length errors, because they will fail again and waste capacity.
Why add jitter to backoff?
Jitter spreads retries over time. Without it, many clients retry simultaneously and cause repeated spikes that keep the service saturated.
How can I stay under token limits?
Estimate token use before each call, share a limiter across instances, and queue lower-priority work to smooth demand over time.
What is a good fallback for rate limits?
A secondary model, a cached or simplified response, or a clear message asking users to retry. Choose based on the feature's importance.
Summary
Understand your request and token limits, retry only retryable errors with backoff and jitter, and control demand with throttling, queues, and priorities. Add fallbacks and monitor limit-related metrics. Consult your provider documentation for exact limits and headers.