고성능 아키텍처를 위한 Rate Limiting 전략: 왜 Spring이 아닌 NGINX+Redis+Lua인가?

진성대·2025년 3월 27일
0

사이드 프로젝트

목록 보기
6/7

대규모 트래픽 환경에서 애플리케이션 서버가 처리해야 하는 과제 중 하나는 과도한 요청(Overload)으로부터 시스템을 보호하고 안정적인 서비스 운영을 보장하는 것입니다. 많은 개발자들은 일반적으로 서버 내부에서, 특히 Spring의 Servlet Filter나 Interceptor를 통해 손쉽게 Rate Limiting을 구현합니다. 하지만 저는 보다 높은 수준의 아키텍처 설계를 위해 NGINX(OpenResty)와 Redis + Lua 스크립트를 이용한 독립된 레이어로 Rate Limiting을 구현하는 방식을 선택하게 되었습니다.


1. 왜 애플리케이션 서버가 아니라 별도의 레이어인가?

일반적으로 Spring 기반의 Filter에서 Rate Limiting을 구현하는 것은 직관적이고 쉽습니다. 간단한 어노테이션으로도 빠르게 구현할 수 있고, 별도의 아키텍처 변경 없이 편리하게 적용할 수 있기 때문입니다.

하지만 이러한 접근 방식은 다음과 같은 문제점을 내포합니다.

  • 리소스 소모 문제 : Rate Limiting을 서버 레벨에서 처리하면, 유입된 요청이 이미 애플리케이션 서버의 자원을 소모한 상태에서 처리됩니다. 이는 Rate Limiting 로직 자체가 이미 서버 리소스를 일부 소비한 후 적용되는 모순적인 상황을 만듭니다.
  • 스케일 아웃의 어려움 : 서버가 수평적으로 확장(scale-out)될 때, 서버마다 독립적으로 Rate Limiting이 수행되기 때문에 중앙집중적이고 일관된 제한 처리가 어렵습니다.

이런 이유로, 저는 Rate Limiting 책임을 애플리케이션 서버로부터 분리하여 별도의 독립된 계층에서 수행하는 것이 맞다고 판단했습니다.


2. 왜 NGINX인가?

NGINX는 웹 서버로서 Reverse Proxy의 역할 뿐 아니라 부하 분산(Load Balancing), 캐싱, SSL Termination 등 다양한 기능을 수행합니다. 하지만 저는 NGINX의 가장 강력한 장점으로 다음을 꼽습니다.

  • Event-driven Non-blocking I/O 모델: NGINX는 Non-blocking 방식으로 많은 요청을 효과적으로 처리할 수 있는 높은 동시성(concurrency)을 보장합니다. 이는 Rate Limiting과 같은 짧고 빈번한 요청 처리에 매우 적합합니다.
  • Middleware로서의 확장성: 특히 OpenResty와 결합했을 때 Lua 스크립트를 통한 매우 유연한 커스터마이징이 가능해, 복잡한 Rate Limiting 로직도 깔끔하게 구현할 수 있습니다.

NGINX를 사용함으로써 애플리케이션 서버가 요청 처리 부담을 덜고 핵심 비즈니스 로직에만 집중할 수 있게 됩니다.


3. 왜 Redis + Lua인가?

Redis는 분산 시스템에서 키-값 저장소(key-value store)로서 빠른 읽기/쓰기가 가능하며, Expiration(Time-To-Live; TTL) 기능을 내장해 Rate Limit 관리에 매우 적합합니다. 여기서 제가 특히 강조하고 싶은 부분은 Redis의 높은 처리 성능과 Lua 스크립트의 원자성(atomicity)입니다.

  • 원자성 보장 : Redis 내에서 Lua 스크립트는 원자적으로 실행되며, 이는 다수의 NGINX 인스턴스들이 같은 Redis 인스턴스를 바라볼 때에도 레이스 컨디션(race condition) 없이 일관된 요청 카운트 관리가 가능합니다.
  • 낮은 지연(latency) : Lua를 통한 Redis 접근은 매우 빠른 응답 속도를 보장하며, 이는 고성능이 필수적인 Rate Limit 처리에 유리합니다.
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 규칙을 완벽히 준수하게 됩니다.


4. 기술적 결정에 대한 아키텍처 비교

방식장점단점
Spring Filter 기반쉬운 구현, 직관성서버 자원 소모, 분산 처리 불가능, 스케일 아웃 어려움
NGINX(OpenResty)+Redis+Lua높은 성능, 원자적 분산 처리, 스케일 아웃 가능성초기 설정의 복잡성, 추가적인 레이어 구성 필요

결국 저는 초기 설정의 다소 복잡함에도 불구하고 장기적으로 시스템이 감당할 수 있는 트래픽 규모와 안정성을 고려할 때 NGINX(OpenResty)와 Redis+Lua를 활용하는 방식을 선택했습니다.


5. 실제 적용 사례

현재 진행 중인 이커머스 프로젝트에서는 초당 수천 건 이상의 트래픽이 발생하는 상황을 대비해 이러한 아키텍처를 채택했습니다. 결과적으로 애플리케이션 서버는 보다 중요한 비즈니스 로직과 데이터 처리에 집중하고 있으며, NGINX + Redis + Lua의 독립적 레이어는 분산 환경에서도 안정적이고 효율적인 Rate Limiting을 제공하고 있습니다.

  • 사용자는 NGINX(OpenResty)를 통해 진입하며, Lua 스크립트가 Redis와 연동하여 요청의 횟수를 계산합니다.
  • Redis는 원자적인 연산을 통해 카운트를 유지하며, TTL 만료 시 카운트가 초기화됩니다.
  • Rate Limit 초과 시 HTTP 429 상태 코드가 즉시 클라이언트로 반환되어 서버 자원 소모 없이 차단됩니다.
profile
주니어 개발자

0개의 댓글