본문으로 건너뛰기
김신건의 로그

[Concurrency] Rate Limiting: token bucket, sliding window

· 수정 · 📖 약 1분 · 452자/단어 #rate-limit #throttling #concurrency #api #backend
Rate Limiting, Token Bucket, Leaky Bucket, Sliding Window, Fixed Window, GCRA, 429 Too Many Requests

정의

Rate Limiting = 시간당 요청 수 제한. API 보호, fair use, 비용 제어, DDoS 완화.

5가지 알고리즘

flowchart LR
    A[Fixed Window]
    B[Sliding Log]
    C[Sliding Window Counter]
    D[Token Bucket]
    E[Leaky Bucket]
    F[GCRA]

1. Fixed Window

[00:00 ~ 00:01] count = 100 (한도)
[00:01 ~ 00:02] count 리셋 → 100
key = f"rl:{user}:{minute_bucket}"
count = redis.incr(key)
redis.expire(key, 60)
if count > 100: deny()
  • 단순.
  • 경계 burst: 00:00:59 에 100 + 00:01:00 에 100 = 2초 안에 200.

2. Sliding Log

모든 요청의 timestamp 저장 → 윈도우 안 카운트.

key = f"rl:log:{user}"
now = time.time()
redis.zadd(key, { req_id: now })
redis.zremrangebyscore(key, 0, now - 60)
count = redis.zcard(key)
if count > 100: deny()
  • 정확.
  • 메모리 큼 (요청마다 entry).

3. Sliding Window Counter

Fixed Window + 이전 window 비율.

current_count + previous_count * (1 - elapsed_in_current/window)
  • 근사.
  • 적은 메모리 + Fixed 의 burst 완화.

4. Token Bucket

flowchart LR
    Refill[1초마다 토큰 N개] --> Bucket[(Bucket: max 100)]
    Bucket -->|토큰 1개 소비| Request[요청 통과]
    Bucket -->|토큰 없음| Deny[429]
  • Burst 허용 (bucket 차있을 때).
  • 평균 rate 보장.
  • AWS / GitHub / Discord API 표준.

5. Leaky Bucket

flowchart TB
    Req[요청] -->|채움| B[Bucket]
    B -->|일정 rate 로 소비| Output[처리]
    B -.넘침.-> Drop[Drop]
  • 완전 평탄화. burst 흡수 + 일정 출력.
  • queue 기반.

6. GCRA (Generic Cell Rate Algorithm)

TAT (Theoretical Arrival Time) 계산
요청 시각이 TAT 보다 충분히 앞이면 허용
  • Token Bucket 수학적 변형.
  • 단일 변수 로 표현. Redis 1 명령 으로 가능.
  • Cloudflare, Stripe 가 사용.

비교 매트릭스

알고리즘메모리정확도Burst구현
Fixed Window적음낮음 (경계 burst)가장 단순
Sliding Log정확없음Sorted Set
Sliding Window Counter적음보통작음2 counter
Token Bucket적음좋음허용간단
Leaky Bucket중간좋음평탄Queue
GCRA최소 (단일 변수)정확가능수학적

분산 환경 구현 (Redis)

# Token Bucket (Lua atomic)
TOKEN_BUCKET_LUA = """
local key = KEYS[1]
local now = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])         -- tokens per sec
local capacity = tonumber(ARGV[3])

local last = redis.call('HMGET', key, 'tokens', 'last')
local tokens = tonumber(last[1]) or capacity
local last_ts = tonumber(last[2]) or now

local delta = math.max(0, now - last_ts) * rate
tokens = math.min(capacity, tokens + delta)
local allowed = tokens >= 1
if allowed then tokens = tokens - 1 end

redis.call('HMSET', key, 'tokens', tokens, 'last', now)
redis.call('EXPIRE', key, math.ceil(capacity / rate * 2))

return allowed and 1 or 0
"""

IMPORTANT

분산 rate limit 는 Redis 한 곳 으로 원자적 카운터. 분산 락 없이 Lua 스크립트.

HTTP 응답 표준

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1719318060

{"error": "rate_limit_exceeded", "retry_after": 30}

어디서?

flowchart LR
    Client --> Edge["CDN / WAF<br/>(IP 단위)"]
    Edge --> GW["API Gateway<br/>(API key 단위)"]
    GW --> App["App<br/>(user 단위)"]
    App --> DB["DB<br/>(쿼리 단위)"]
레이어단위
CDNIP, geographic
WAF의심 패턴
API GatewayAPI key, OAuth token
Appuser, tenant
DBquery, connection

Tier-based Rate Limit

limits = {
    "free":    {"per_min": 60, "burst": 100},
    "pro":     {"per_min": 600, "burst": 1000},
    "enterprise": {"per_min": 10000, "burst": 50000},
}

Stripe, OpenAI 같은 유료 API 의 표준.

흔한 함정

WARNING

  1. 단일 노드 in-memory counter = N 노드면 N배 한도. 분산 store 필수.
  2. IP 기반만 = NAT 뒤 수천 사용자가 한 IP. user + IP 조합.
  3. Retry-After 무시 (클라이언트) = 무한 retry → DDoS 자기 자신.
  4. Burst 너무 큼 = bucket 가득 차면 순간 throughput 폭증. 백엔드 보호 안 됨.

관련 위키

이 글의 용어 (6개)
[Concurrency] Backpressure: 흐름 제어로 시스템 보호concurrency
정의 Backpressure = 생산자가 소비자 속도에 맞춰 자기 속도 조절. 분산 시스템의 cascade failure 방지. [!IMPORTANT] Backpressure 의…
[Concurrency] Circuit Breaker: cascade failure 방어concurrency
정의 Circuit Breaker = 전기 회로의 차단기처럼, 백엔드 다운 / 느림 시 호출 자체를 차단 → 빠른 실패 + 백엔드 회복 시간. 3가지 상태 | 상태 | 의미 | …
[Concurrency] Retry + Exponential Backoff + Jitterconcurrency
정의 Retry with Exponential Backoff + Jitter = 실패 시 점점 긴 간격으로 재시도, 랜덤 분산. 분산 시스템의 thundering herd / r…
[Pattern] API Gateway: BFF, 라우팅, auth, rate limitdistributed-systems
정의 API Gateway = 클라이언트와 마이크로서비스 사이의 단일 진입점. 라우팅, auth, rate limit, transformation, monitoring 의 cro…
[Redis] Sorted Set: Skiplist + Dict, 리더보드, 우선순위 큐database-internals
정의 Redis Sorted Set (ZSet) 은 멤버 + 점수 (score) 의 순서 보존 unique 집합. score 기준 정렬 + 멤버 기준 O(1) 조회 가 동시에 가…
[Redis] String / Bitmap / Bitfield: 가장 기본 + 가장 강력database-internals
정의 Redis String 은 binary-safe 한 바이트 열. 최대 512 MB. 문자열만이 아니라 정수, 부동소수, 직렬화된 객체, 비트맵 (Bitmap), 패킹된 정수…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기