대규모 트래픽 환경에서 애플리케이션 서버가 처리해야 하는 과제 중 하나는 과도한 요청(Overload)으로부터 시스템을 보호하고 안정적인 서비스 운영을 보장하는 것입니다. 많은 개발자들은 일반적으로 서버 내부에서, 특히 Spring의 Servlet Filter나 Interceptor를 통해 손쉽게 Rate Limiting을 구현합니다. 하지만 저는 보다 높은 수준의 아키텍처 설계를 위해 NGINX(OpenResty)와 Redis + Lua 스크립트를 이용한 독립된 레이어로 Rate Limiting을 구현하는 방식을 선택하게 되었습니다.
일반적으로 Spring 기반의 Filter에서 Rate Limiting을 구현하는 것은 직관적이고 쉽습니다. 간단한 어노테이션으로도 빠르게 구현할 수 있고, 별도의 아키텍처 변경 없이 편리하게 적용할 수 있기 때문입니다.
하지만 이러한 접근 방식은 다음과 같은 문제점을 내포합니다.
이런 이유로, 저는 Rate Limiting 책임을 애플리케이션 서버로부터 분리하여 별도의 독립된 계층에서 수행하는 것이 맞다고 판단했습니다.
NGINX는 웹 서버로서 Reverse Proxy의 역할 뿐 아니라 부하 분산(Load Balancing), 캐싱, SSL Termination 등 다양한 기능을 수행합니다. 하지만 저는 NGINX의 가장 강력한 장점으로 다음을 꼽습니다.
NGINX를 사용함으로써 애플리케이션 서버가 요청 처리 부담을 덜고 핵심 비즈니스 로직에만 집중할 수 있게 됩니다.
Redis는 분산 시스템에서 키-값 저장소(key-value store)로서 빠른 읽기/쓰기가 가능하며, Expiration(Time-To-Live; TTL) 기능을 내장해 Rate Limit 관리에 매우 적합합니다. 여기서 제가 특히 강조하고 싶은 부분은 Redis의 높은 처리 성능과 Lua 스크립트의 원자성(atomicity)입니다.
local current = redis.call("INCR", KEYS[1])
if current == 1 then
redis.call("EXPIRE", KEYS[1], tonumber(ARGV[1]))
end
if current > tonumber(ARGV[2]) then
return 429 -- Rate limit exceeded
else
return 200 -- Allowed
end
Redis + Lua의 이 간단한 구조만으로도 다수의 서버가 단일한 Rate Limit 규칙을 완벽히 준수하게 됩니다.
방식 | 장점 | 단점 |
---|---|---|
Spring Filter 기반 | 쉬운 구현, 직관성 | 서버 자원 소모, 분산 처리 불가능, 스케일 아웃 어려움 |
NGINX(OpenResty)+Redis+Lua | 높은 성능, 원자적 분산 처리, 스케일 아웃 가능성 | 초기 설정의 복잡성, 추가적인 레이어 구성 필요 |
결국 저는 초기 설정의 다소 복잡함에도 불구하고 장기적으로 시스템이 감당할 수 있는 트래픽 규모와 안정성을 고려할 때 NGINX(OpenResty)와 Redis+Lua를 활용하는 방식을 선택했습니다.
현재 진행 중인 이커머스 프로젝트에서는 초당 수천 건 이상의 트래픽이 발생하는 상황을 대비해 이러한 아키텍처를 채택했습니다. 결과적으로 애플리케이션 서버는 보다 중요한 비즈니스 로직과 데이터 처리에 집중하고 있으며, NGINX + Redis + Lua의 독립적 레이어는 분산 환경에서도 안정적이고 효율적인 Rate Limiting을 제공하고 있습니다.