API 속도 제한 및 조절 전문가은(는) 무엇을 하나요?
토큰 버킷, 슬라이딩 윈도우, 고정 윈도우 알고리즘과 Redis 기반 분산 카운터를 사용하여 공정하고 효율적인 속도 제한을 구현합니다.
활성화 트리거
활성화 키워드: "rate limiting", "throttling", "token bucket", "sliding window", "API abuse", "DDoS protection", "quota management", "429 Too Many Requests", "request limits"
사용하지 않을 때: API 게이트웨이 설정 → api-gateway-reverse-proxy-expert | 캐싱 전략 → cache-strategy-invalidation-expert | WAF/방화벽 규칙 → 관련 보안 기술
빠른 시작
- 제한 정의 — API 키, IP, 또는 사용자 계층별 분당 요청 수 설정
- 알고리즘 선택 — 슬라이딩 윈도우 로그 (정확), 토큰 버킷 (버스트 처리), 고정 윈도우 (간단)
- Redis 사용 — 분산 속도 제한을 위한
MULTI/EXEC또는 Lua 스크립트의 원자적 연산 활용 - 올바른 헤더 반환 —
X-RateLimit-Limit,X-RateLimit-Remaining,Retry-After - 계층 구분 — 무료/프로/엔터프라이즈에 다른 제한 적용
핵심 기능
| 도메인 | 기술 |
|---|---|
| 알고리즘 | 토큰 버킷, 슬라이딩 윈도우 로그, 슬라이딩 윈도우 카운터, 고정 윈도우 |
| 저장소 | Redis 7.4+, Valkey, DragonflyDB, 인-메모리 (단일 노드) |
| 라이브러리 | rate-limiter-flexible, @upstash/ratelimit, express-rate-limit |
| 게이트웨이 플러그인 | Kong rate-limiting, Nginx limit_req, Traefik ratelimit |
| 표준 | RFC 6585 (429), RateLimit 헤더 (draft-ietf-httpapi-ratelimit) |
아키텍처 패턴
슬라이딩 윈도우 카운터 (Redis Lua)
-- Redis Lua 스크립트: 슬라이딩 윈도우 속도 제한기
-- KEYS[1] = 속도 제한 키
-- ARGV[1] = 윈도우 크기 (초)
-- ARGV[2] = 최대 요청 수
-- ARGV[3] = 현재 타임스탬프
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
-- 만료된 항목 제거
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- 현재 윈도우 카운트
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. '-' .. math.random(1000000))
redis.call('EXPIRE', key, window)
return {1, limit - count - 1} -- 허용, 남은 횟수
else
return {0, 0} -- 거부, 0회 남음
end
다중 계층 속도 제한
요청 → IP 속도 제한 (100/분)
│ 통과
↓
인증 확인 → API 키 속도 제한 (계층 기반)
│ 무료: 60/분
│ 프로: 600/분
│ 엔터프라이즈: 6000/분
↓ 통과
엔드포인트 속도 제한 (경로별)
│ POST /upload: 10/분
│ GET /search: 120/분
↓ 통과
요청 처리
응답 헤더 (IETF 초안 표준)
// 미들웨어: 속도 제한 헤더 첨부
function rateLimitHeaders(limit: number, remaining: number, resetAt: number) {
return {
'RateLimit-Limit': limit.toString(),
'RateLimit-Remaining': Math.max(0, remaining).toString(),
'RateLimit-Reset': Math.ceil((resetAt - Date.now()) / 1000).toString(),
};
}
// 429 응답 시:
res.status(429).set({
...rateLimitHeaders(limit, 0, resetAt),
'Retry-After': Math.ceil((resetAt - Date.now()) / 1000).toString(),
}).json({ error: 'Too Many Requests', retryAfter: resetAt });
안티 패턴
- 분산 시스템에서의 인-메모리 카운터 — 속도 제한은 인스턴스 간에 공유되어야 합니다. Redis 또는 동등한 저장소를 사용하세요.
- Retry-After 헤더 누락 —
Retry-After없이 429 응답을 보내면 클라이언트가 추측하여 썬더링 허드(동시 폭주)를 유발합니다. - IP 기반 단일 속도 제한 — IP 제한만으로는 인증된 남용을 놓치고 공유 IP(NAT, VPN)를 사용하는 사용자를 처벌합니다. API 키 제한과 결합하세요.
- 버스트 없이 하드 컷오프 — 토큰 버킷은 평균 속도를 유지하면서 작은 버스트를 허용합니다. 순수 고정 윈도우는 너무 경직됩니다.
- 내부 API에 속도 제한 없음 — 잘못 동작하는 내부 서비스가 데이터베이스에 연속 장애를 일으킬 수 있습니다. 항상 제한을 두세요.
품질 체크리스트
- 속도 제한은 분산 저장소(Redis/Valkey)를 사용하며, 인-메모리를 사용하지 않습니다.
- 모든 응답에
RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset헤더를 포함합니다. - 429 응답에
Retry-After헤더를 포함합니다. - 인증 계층별로 다른 제한을 적용합니다 (무료/프로/엔터프라이즈).
- 비용이 많이 드는 작업(업로드, 검색)에 대해 엔드포인트별 제한을 둡니다.
- 카운터 증가에 Lua 스크립트 또는 원자적 파이프라인을 사용합니다 (경쟁 조건 없음).
- 속도 제한 키에는 ID(API 키)와 범위(엔드포인트)가 모두 포함됩니다.
- 모니터링 대시보드에서 속도 제한 적중, 거부, 상위 소비자를 확인할 수 있습니다.
- 정격 저하: 가능한 경우 하드 리젝트 대신 큐에 넣거나 속도를 늦춥니다.