한양과학기술고등학교

에듀테크 프로젝트 대시보드

레이트 리미팅 (토큰 버킷)

레이트 리미팅 (Rate Limiting)

서버는 초당 처리할 수 있는 요청 수가 정해져 있다. 한 사용자가 1초에 수천 번 요청을 퍼부으면 (실수한 클라이언트, 크롤러, 혹은 공격) 다른 모든 사용자가 느려지거나 서버가 멈춘다. 레이트 리미팅은 "일정 시간 동안 허용하는 요청 수"에 상한을 두고, 넘치면 429 Too Many Requests 로 돌려보내는 방어 장치다. 가장 널리 쓰이는 토큰 버킷(Token Bucket) 알고리즘을 직접 돌려보자.

토큰 버킷 시뮬레이터 — 버튼을 연타해 버킷을 비워 보자

10 개
3 /초
6 /초
10 / 10
남은 토큰
요청이 오면 토큰 1개를 소모한다. 토큰이 없으면 거절(429).
허용 (200 OK)
0
거절 (429)
0
거절률
0%

토큰은 매 순간 refill 속도로 다시 차오르지만 capacity를 넘지 못한다. 한동안 요청이 없으면 버킷이 가득 차서, 갑자기 몰린 요청(burst)을 용량만큼은 즉시 받아준다. 그 뒤로는 채워지는 속도만큼만 통과한다. → 평소엔 여유, 순간 폭주도 어느 정도 허용.

시간에 따른 허용 / 거절 (자동 트래픽을 켜고 관찰)

파란 선(들어온 요청)이 주황 선(refill 속도)보다 높게 유지되면, 초반 버킷을 다 쓴 뒤부터 초과분이 빨간 영역(429)으로 잘려 나간다.

4가지 대표 알고리즘

알고리즘아이디어장점단점
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와 함께, "몇 초 뒤 다시"

착한 클라이언트는 이 값을 보고 스스로 속도를 늦춘다(백오프).

어디서 거는가
  • Nginx limit_req_zone — 앱에 닿기 전 1차 차단
  • API 게이트웨이 / CDN(Cloudflare 등) — 엣지에서
  • 앱 미들웨어 — 로그인·결제 등 민감 엔드포인트별 세밀 제어
  • 키 기준: IP, API 키, 사용자 ID, 엔드포인트 조합

이 사이트도 앱 앞단에서 과도한 요청을 정리한다.

주의: 여러 사용자가 하나의 회사 IP(NAT) 뒤에 있으면 IP 기준은 억울한 차단을 낳는다. 로그인 사용자는 계정 기준, 익명은 IP 기준으로 나눠 거는 게 보통이다.