대규모 트래픽을 처리하는 이커머스 시스템에서 특정 API에 대한 과도한 호출은 시스템의 성능 저하와 서비스 장애를 유발할 수 있습니다.
이러한 문제를 방지하고 안정적인 서비스 운영을 보장하기 위해서는 효율적이고 확장 가능한 Rate Limiting 전략이 필수적입니다.
본 프로젝트에서는 이를 해결하기 위해 다음과 같은 기술 스택을 활용하여 Rate Limiting을 구현했습니다.
기존의 Nginx 내장 방식만을 사용한 Rate Limiting은 다음과 같은 문제점이 존재합니다.
문제점 | 내용 |
---|---|
단일 서버 종속성 | Nginx 내장 모듈을 사용하면 각 인스턴스마다 독립적으로 동작해 분산 환경에서 통합된 제한이 어려움 |
유연성 부족 | URI별, 메소드별 세부 제어가 어렵고, 동적으로 변경이 어려움 |
확장성 한계 | 트래픽 증가 시 개별 Nginx 인스턴스의 설정 관리가 비효율적 |
위 문제를 해결하기 위해 Nginx(OpenResty) 환경에서 Lua 스크립트로 Redis를 연동하여 요청 빈도를 관리하는 분산 Rate Limiting을 도입하였습니다.
구성 요소 | 역할 및 특징 |
---|---|
OpenResty (Nginx + Lua) | Lua를 통해 요청이 들어올 때마다 Redis로 접근하여 빈도를 계산하고 제한 여부 판단 |
Redis | 각 API 요청에 대한 빈도 카운트(INCR) 및 TTL 기반의 만료 관리(EXPIRE) |
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
-- Redis 연결
local ok, err = red:connect("redis", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis 연결 실패: ", err)
return ngx.exit(500)
end
-- GET 요청은 제한에서 제외
if ngx.req.get_method() == "GET" then
return
end
-- 요청을 구분하는 고유 키 생성 (메소드 + URI + 클라이언트 IP)
local key = "ratelimit:" .. ngx.req.get_method() .. ":" .. ngx.var.uri .. ":" .. ngx.var.binary_remote_addr
-- 제한 기준 설정 (예: 10초에 최대 5번)
local limit = 5
local window = 10
-- 요청 횟수 증가 및 체크
local count, err = red:incr(key)
if not count then
ngx.log(ngx.ERR, "Redis 카운트 증가 실패: ", err)
return ngx.exit(500)
end
-- TTL이 없는 경우 새로 설정
local ttl, err = red:ttl(key)
if ttl == -1 then
red:expire(key, window)
end
-- 요청 제한 초과 시 429 반환
if count > limit then
ngx.status = 429
ngx.say("Rate limit exceeded.")
return ngx.exit(429)
end
개선 항목 | 효과 |
---|---|
분산 Rate Limiting 지원 | Redis를 통해 여러 Nginx 서버에서 통합적으로 제한 관리 가능 |
실시간성 | Redis의 TTL 기반 캐싱으로 최신 상태의 빠른 조회 및 정확한 제한 적용 |
유연성 및 확장성 | 메소드, URI, 클라이언트 IP 기반으로 제한을 매우 세부적이고 동적으로 관리 가능 |