API Rate Limits: Shared Quotas, Token Buckets and Safe Retry Decisions
TECH INSIGHT / EXPLAINED
A rate limiter controls when a request may be attempted. A retry policy separately decides whether repeating that operation is safe. Mixing the two can turn a transient timeout into a duplicate order.
Match the quota the server actually counts
Identify the quota key: IP, account, API key, endpoint group or request weight. A process-local bucket does not coordinate several bot instances sharing that quota. Reserve scheduler capacity for reconciliation and cancellation instead of letting background reports consume everything.
A token bucket with refill r and capacity B allows a burst as well as a long-run rate. It does not guarantee “at most N in every one-second window.” Choose the model and burst against the server’s actual contract; extra local buckets never create extra server quota.
Go implementation and cancellation
golang.org/x/time/rate supports Allow, Reserve and Wait; WaitN can account for weighted requests within the configured burst. Pass a context deadline. Include the waiting time in the total request budget and use cancellation-aware timers for retries, rather than an unconditional sleep after shutdown.
For a safe read, use bounded exponential backoff with jitter. Set maximum attempts and a total deadline. Do not sleep again after the final attempt; return the last meaningful error with context.
Retry-After has two formats
An HTTP Retry-After value can be seconds or an HTTP date. Parse both. Treat it as the earliest retry time: if it exceeds the operation deadline, return a deferred/failure result rather than silently truncating the server’s wait and retrying too early.
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
def retry_after_seconds(value, now):
if not value:
return None
value = value.strip()
if value.isascii() and value.isdigit():
return float(int(value))
try:
target = parsedate_to_datetime(value)
if target.tzinfo is None:
return None
return max(0.0, (target-now).total_seconds())
except (TypeError, ValueError, OverflowError):
return None
now = datetime(2026, 9, 29, 0, 0, tzinfo=timezone.utc)
assert retry_after_seconds('5', now) == 5
assert retry_after_seconds('Tue, 29 Sep 2026 00:00:10 GMT', now) == 10A timeout after an order is not a retry instruction
A read timeout or some 5xx responses may occur after the order was accepted. For order creation, reconcile using the venue’s documented client ID/idempotency behavior before any repeat. Some IDs only assist lookup and do not guarantee idempotency. Permanent parameter errors need correction, not more attempts.
Monitor throttled counts, quota remaining where available, queue age and reconciliation delay. Self-throttling reduces avoidable violations but cannot promise that a shared quota will never be exhausted.
Sources and further reading
Go rate limiter · HTTP Retry-After · Requests error-handling companion
Content and examples reviewed: 2026-09-29
댓글
댓글 쓰기