에듀테크 프로젝트 대시보드
레이트 리미팅 (토큰 버킷)
서버는 초당 처리할 수 있는 요청 수가 정해져 있다. 한 사용자가 1초에 수천 번 요청을 퍼부으면 (실수한 클라이언트, 크롤러, 혹은 공격) 다른 모든 사용자가 느려지거나 서버가 멈춘다. 레이트 리미팅은 "일정 시간 동안 허용하는 요청 수"에 상한을 두고, 넘치면 429 Too Many Requests 로 돌려보내는 방어 장치다. 가장 널리 쓰이는 토큰 버킷(Token Bucket) 알고리즘을 직접 돌려보자.
토큰은 매 순간 refill 속도로 다시 차오르지만 capacity를 넘지 못한다. 한동안 요청이 없으면 버킷이 가득 차서, 갑자기 몰린 요청(burst)을 용량만큼은 즉시 받아준다. 그 뒤로는 채워지는 속도만큼만 통과한다. → 평소엔 여유, 순간 폭주도 어느 정도 허용.
파란 선(들어온 요청)이 주황 선(refill 속도)보다 높게 유지되면, 초반 버킷을 다 쓴 뒤부터 초과분이 빨간 영역(429)으로 잘려 나간다.
| 알고리즘 | 아이디어 | 장점 | 단점 |
|---|---|---|---|
| Token Bucket | 버킷에 토큰이 일정 속도로 쌓임. 요청마다 1개 소모, 없으면 거절. | 순간 폭주 허용, 메모리 O(1), 구현 단순 | 시계 동기화·분산 환경에서 정확도 관리 필요 |
| Leaky Bucket | 요청을 큐에 담고 일정 속도로만 빠져나감(누수). 큐가 꽉 차면 거절. | 출력 속도가 완전히 일정 → 뒷단 보호에 유리 | 폭주를 못 받아줌, 큐 대기로 지연 증가 |
| Fixed Window | "매 분 100건"처럼 고정 구간마다 카운터를 0으로 리셋. | 가장 구현 쉬움, 카운터 1개 | 구간 경계에서 2배 폭주 가능(59초·0초에 몰림) |
| Sliding Window | 최근 N초 동안의 요청을 로그/가중 카운터로 계산해 항상 "지금 기준" 판정. | 경계 폭주 없음, 가장 정확 | 로그 방식은 메모리 큼(카운터 방식으로 근사) |
토큰 버킷 vs 리키 버킷: 둘은 사실 동전의 양면이다. 토큰 버킷은 "토큰이 쌓이고 요청이 소모", 리키 버킷은 "요청이 쌓이고 시간이 소모". 토큰 버킷은 버스트 허용, 리키 버킷은 출력 평활화에 방점.
import time class TokenBucket: def __init__(self, capacity, refill_per_sec): self.capacity = capacity # 버킷 최대 토큰 수 self.rate = refill_per_sec # 초당 채워지는 양 self.tokens = capacity # 처음엔 가득 self.updated = time.monotonic() def allow(self, cost=1): now = time.monotonic() # 마지막 호출 이후 흐른 시간만큼 토큰을 채운다 (상한 = capacity) self.tokens = min(self.capacity, self.tokens + (now - self.updated) * self.rate) self.updated = now if self.tokens >= cost: self.tokens -= cost return True # 200 OK return False # 429 Too Many Requests # 사용자별 버킷을 dict에 두고 IP/API 키로 조회 buckets = {} def check(key): b = buckets.setdefault(key, TokenBucket(capacity=10, refill_per_sec=3)) return b.allow()
핵심 트릭: 주기적으로 토큰을 더하는 백그라운드 작업이 필요 없다. 요청이 들어온 순간
"지난번 이후 몇 초 지났으니 그만큼 채운다"라고 계산하면 끝. 상태는 tokens,
updated 두 값뿐이라 Redis 같은 곳에 저장해 여러 서버가 공유하기도 쉽다.
X-RateLimit-Limit — 구간당 허용량X-RateLimit-Remaining — 남은 횟수X-RateLimit-Reset — 리셋까지 남은 초Retry-After — 429와 함께, "몇 초 뒤 다시"착한 클라이언트는 이 값을 보고 스스로 속도를 늦춘다(백오프).
limit_req_zone — 앱에 닿기 전 1차 차단이 사이트도 앱 앞단에서 과도한 요청을 정리한다.
주의: 여러 사용자가 하나의 회사 IP(NAT) 뒤에 있으면 IP 기준은 억울한 차단을 낳는다. 로그인 사용자는 계정 기준, 익명은 IP 기준으로 나눠 거는 게 보통이다.