You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

38 lines
1.4 KiB
Python

# backend/app/connectors/ratelimit.py — 토큰 버킷 + 429/Retry-After 지수 백오프 (phase-13)
import threading
import time
import httpx
class RateLimiter:
"""간단 토큰 버킷 + 429/Retry-After 지수 백오프."""
def __init__(self, rate: float = 4, per: float = 1.0, max_backoff: float = 32.0):
self.rate, self.per = rate, per
self.allowance = rate
self.last = time.monotonic()
self.max_backoff = max_backoff
self._lock = threading.Lock()
def acquire(self) -> None:
with self._lock:
now = time.monotonic()
self.allowance += (now - self.last) * (self.rate / self.per)
self.last = now
if self.allowance > self.rate:
self.allowance = self.rate
if self.allowance < 1.0:
time.sleep((1.0 - self.allowance) * (self.per / self.rate))
self.allowance = 0.0
else:
self.allowance -= 1.0
def handle_response(self, r: "httpx.Response", attempt: int = 0) -> bool:
"""429 면 Retry-After(또는 지수 백오프)만큼 대기 후 True(재시도) 반환."""
if r.status_code == 429:
retry = float(r.headers.get("Retry-After", min(2**attempt, self.max_backoff)))
time.sleep(min(retry, self.max_backoff))
return True
return False