본문 바로가기

Study

[내일배움캠프 TIL] Spring Cloud Gateway Rate Limiting에 Redis가 필요한 이유

728x90
반응형

배경

Spring Cloud Gateway에 Rate Limiting을 적용하려고 보니, 공식 구현체가 Redis를 필수로 요구함.

처음엔 "왜 게이트웨이에 Redis가 필요하지?" 라는 의문이 생겼고, 이를 정리함.


핵심 질문

게이트웨이에 Rate Limiting을 넣으려면 Redis가 꼭 필요한가?

결론부터: Spring Cloud Gateway 내장 방식은 필요, in-memory 방식은 불필요.


Spring Cloud Gateway의 Rate Limiting 동작 방식

Spring Cloud Gateway는 RequestRateLimiter 필터를 통해 Rate Limiting을 제공함.

내부적으로 Token Bucket 알고리즘을 사용하고, 유저별 토큰 상태를 Redis에 저장함.

Token Bucket이란?

  • 버킷에 토큰이 일정 속도로 채워짐 (replenishRate: 초당 N개)
  • 요청이 들어오면 토큰 1개 소비
  • 버킷이 비면 요청 거부 → 429 Too Many Requests
  • 최대 버킷 크기(burstCapacity)로 순간 burst 허용
[유저 A]
버킷: ████████░░  (8/10)
요청 → 토큰 1 소비 → 7/10

초당 N개씩 보충 ↑

왜 Redis에 저장하는가?

이유 설명

분산 환경 대응 게이트웨이를 여러 대 띄울 때 인스턴스 간 상태 공유 필요
재시작 후 유지 in-memory는 재시작 시 카운트 초기화됨
원자적 연산 Redis의 Lua 스크립트로 토큰 차감을 원자적으로 처리 (동시성 안전)

방법 비교

방법 Redis 필요 분산 환경 비고

Gateway 내장 RequestRateLimiter 필요 ✅ 가능 공식 지원, Token Bucket
Bucket4j (in-memory) 불필요 ❌ 불가 단일 인스턴스 전용
커스텀 GlobalFilter • ConcurrentHashMap 불필요 ❌ 불가 직접 구현, 단순한 경우만

이 프로젝트에서의 판단

  • omc-redis가 이미 운영 중 → 추가 인프라 비용 없음
  • 다른 서비스(coupon, order 등)는 이미 Redis 사용 중
  • 향후 게이트웨이 수평 확장 가능성 고려
  • Spring Cloud Gateway 내장 방식 + Redis 채택

단, gateway 서비스에 Redis 연결이 빠져 있었음

다른 서비스들은 docker-compose.services.yml에 Redis 환경변수가 설정되어 있으나,

gateway는 기존에 Redis를 쓸 일이 없어서 연결 설정이 없었음.

Rate Limiting 구현 시 아래 작업이 함께 필요:

  1. build.gradle에 spring-boot-starter-data-redis-reactive 추가
  2. docker-compose.services.yml gateway 서비스에 REDIS_HOST, REDIS_PORT 환경변수 추가
  3. gateway.yml에 spring.data.redis 연결 설정 추가

주의사항

  • Fail-Open: Redis가 다운되면 rate limit이 걸리지 않고 모든 요청이 통과됨 (Spring Cloud Gateway 기본 동작)
  • 프로덕션에서는 Redis HA(Sentinel 또는 Cluster) 구성 고려 필요
  • KeyResolver에서 인증 유저는 Keycloak UUID, 미인증은 IP로 분리해야 유저 단위 제한이 가능함

버킷은 유저마다 따로 있음

헷갈리기 쉬운 포인트: 버킷 하나를 전체 유저가 나눠 쓰는 게 아니라, 유저마다 버킷이 1개씩 독립 생성됨.

Redis 저장 구조

request_rate_limiter.{raffle-service:유저A-uuid}.tokens → 8개
request_rate_limiter.{raffle-service:유저B-uuid}.tokens → 3개
request_rate_limiter.{raffle-service:유저C-uuid}.tokens → 0개

유저 C의 버킷이 비어서 429를 받아도 유저 A, B는 전혀 영향 없음.

버킷 생성 시점

유저가 처음 요청할 때 Redis에 해당 유저의 버킷이 자동 생성됨. 사전에 만들어둘 필요 없음.

그림으로 보면

유저 A  →  [████████░░] 8/10  → 요청 통과
유저 B  →  [███░░░░░░░] 3/10  → 요청 통과
유저 C  →  [░░░░░░░░░░] 0/10  → 429 ❌

서비스별로도 분리됨 (routeId 포함 키)

키에 routeId를 포함시키면 같은 유저라도 서비스마다 버킷이 따로 생성됨.

유저 A, 래플 버킷:   request_rate_limiter.{raffle-service:유저A-uuid}.tokens
유저 A, 상품 버킷:   request_rate_limiter.{product-service:유저A-uuid}.tokens

→ 래플 API를 많이 써도 상품 조회 버킷은 그대로 유지됨. 서비스 성격에 맞는 개별 제한이 가능해짐.

반응형