본문 바로가기
Backend2026년 4월 14일5분 읽기

API Rate Limiting 완벽 구현 — Token Bucket·Sliding Window·Redis 실전 코드

YS
김영삼
조회 352

핵심 요약

Rate limiting은 버스트 허용 vs 평활화정확도 vs 메모리의 트레이드오프다. 대부분의 API에는 Token Bucket이 가장 실용적.

알고리즘 4종 비교

알고리즘버스트정확도구현
Fixed Window경계에서 2배낮음매우 쉬움
Sliding Window Log없음최고메모리 큼
Sliding Window Counter거의 없음높음쉬움
Token Bucket제어 가능높음쉬움

Token Bucket (Redis Lua)

-- KEYS[1] = bucket key
-- ARGV[1] = capacity, ARGV[2] = refill_rate (tokens/sec)
-- ARGV[3] = now (ms), ARGV[4] = requested
local capacity = tonumber(ARGV[1])
local rate     = tonumber(ARGV[2])
local now      = tonumber(ARGV[3])
local want     = tonumber(ARGV[4])

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

local delta  = math.max(0, now - last) / 1000
tokens = math.min(capacity, tokens + delta * rate)

local allowed = 0
if tokens >= want then
  tokens = tokens - want
  allowed = 1
end

redis.call('HMSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], 60000)
return {allowed, tokens}

Node.js 래퍼

const script = fs.readFileSync('tokenbucket.lua', 'utf8');
const sha = await redis.scriptLoad(script);

async function check(userId, capacity = 100, rate = 10) {
  const [ok, remaining] = await redis.evalSha(sha, {
    keys: [`rl:${userId}`],
    arguments: [String(capacity), String(rate), String(Date.now()), '1'],
  });
  return { ok: ok === 1, remaining };
}

응답 헤더 규격

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1713234567
Retry-After: 13          # 429 응답 시

키 설계

  • 개인화: rl:user:{id}
  • IP 기반: rl:ip:{ip} (프록시 X-Forwarded-For 주의)
  • 엔드포인트 분리: rl:user:{id}:POST:/v1/transfer
  • 티어: rl:user:{id}:tier:{pro}

분산 환경 주의

  • 레디스 단일 인스턴스 병목 → 샤딩 or Cluster mode
  • 원자성: 반드시 Lua script로 "읽기-수정-쓰기" 묶기
  • clock skew: 서버 로컬 time 사용 금지, Redis TIME 또는 요청 시각 일원화

프레임워크별 구현

  • Nginx: limit_req_zone (leaky bucket, 로컬)
  • Envoy: RateLimitService(rls) 외부 참조
  • Next.js middleware: Edge에서 Upstash Redis 연동
  • FastAPI: slowapi + aiolimiter

자주 묻는 질문

429 vs 503 선택?

클라이언트의 호출량 과다 → 429. 서버 과부하 → 503. 혼용 금지.

DDoS 방어로 충분한가?

아니다. L7 rate limit은 합법 클라이언트 제어용. L3/L4 공격은 WAF/DDoS 보호 서비스가 필요.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록