2 min read
Redis Sliding Window Rate Limit 실전
사용자 단위 호출 제한을 Sliding Window로 구현하고 오탐과 누락을 줄이는 방법

도입
Rate limit는 "막는다"보다 "정상 사용자 경험을 유지한다"가 목표다. 고정 윈도우 방식은 구현이 쉽지만 경계 시점에서 burst를 허용해 오탐/누락이 같이 생긴다. 트래픽이 급한 서비스에서는 이 작은 차이가 장애로 이어진다.
이 글은 Redis ZSET 기반 Sliding Window를 실무에서 안정적으로 적용하는 패턴을 다룬다. 특히 멀티 인스턴스 API 환경에서 원자성, 메모리 관리, 관측 지표를 함께 본다.

문제 정의
현장에서 흔한 문제는 다음과 같다.
- 요청 카운트를
INCR + EXPIRE로만 처리해 윈도우 경계에서 허용량이 튄다. - 애플리케이션 레벨 잠금으로 처리해 API 인스턴스 수가 늘 때 정확도가 떨어진다.
- 제한 초과 응답은 있는데, "어떤 키가 왜 막혔는지"를 추적할 수 없다.
- Redis 키 정리 규칙이 없어 메모리 사용량이 예측되지 않는다.
핵심은 원자성과 운영 가시성을 동시에 확보하는 것이다.
핵심 개념
| 요소 | 권장 선택 | 이유 |
|---|---|---|
| 자료구조 | ZSET (timestamp score) | 구간 삭제와 개수 계산이 단순 |
| 실행 방식 | Lua script | check + insert를 원자적으로 처리 |
| 키 설계 | rl:{scope}:{id} | 다중 정책 병행 운영 용이 |
| 응답 헤더 | X-RateLimit-* | 클라이언트 재시도 전략 연계 |
실무에서는 정책을 1개로 끝내지 않는다. user, ip, token 정책을 분리하고 가장 엄격한 결과를 적용하는 방식이 안전하다.
코드 예시 1: Sliding Window Lua 스크립트
-- KEYS[1]: rate-limit key
-- ARGV[1]: now (ms)
-- ARGV[2]: window (ms)
-- ARGV[3]: limit
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call("ZREMRANGEBYSCORE", key, 0, now - window)
local current = redis.call("ZCARD", key)
if current >= limit then
local oldest = redis.call("ZRANGE", key, 0, 0, "WITHSCORES")
local retryAfter = 0
if oldest[2] ~= nil then
retryAfter = window - (now - tonumber(oldest[2]))
end
return {0, current, retryAfter}
end
redis.call("ZADD", key, now, tostring(now))
redis.call("PEXPIRE", key, window)
return {1, current + 1, 0}
코드 예시 2: Node.js 미들웨어 래퍼
import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL!);
const windowMs = 60_000;
const limit = 120;
export async function applyUserRateLimit(userId: string) {
const key = `rl:user:${userId}`;
const now = Date.now();
const [allowed, current, retryAfter] = (await redis.evalsha(
process.env.RL_SHA!,
1,
key,
now,
windowMs,
limit,
)) as [number, number, number];
return {
allowed: allowed === 1,
remaining: Math.max(0, limit - current),
retryAfterMs: retryAfter,
};
}
아키텍처 흐름
Mermaid diagram rendering...
트레이드오프
- Sliding Window는 정확도가 높지만 Fixed Window보다 연산량이 많다.
- Lua를 쓰면 정확도는 좋아지지만 운영팀이 스크립트 버전 관리 체계를 가져야 한다.
- 정책을 세분화할수록 사용자 보호는 좋아지지만 디버깅 포인트가 늘어난다.
정리
Rate limit는 보안 기능이면서 동시에 UX 기능이다. Redis Sliding Window를 도입할 때는 알고리즘 정확도만 보지 말고, 헤더 표준화와 지표 수집까지 함께 설계해야 실제 운영에서 효과를 본다.
이미지 출처
- Cover: source link
- License: CC BY-SA 3.0 / Author: Esquilo
- Note: Wikimedia Commons 무료 라이선스 이미지를 다운로드 후 1600px 기준 JPG로 최적화했습니다.