Implementing the rate limiter pattern

2025. 3. 16. 17:34Spring Microservice

🚀 [Spring Cloud + Resilience4j] Rate Limiter(속도 제한) 패턴 가이드 (서비스 과부하 방지하기)

지난 포스팅에서 우리는 Resilience4j의 Circuit Breaker, Retry, Bulkhead, Fallback 패턴을 깊이 있게 다뤘습니다. 이번 포스팅에서는 서비스의 호출 속도를 제한하여 과부하를 방지할 수 있는 Rate Limiter(속도 제한) 패턴에 대해 자세히 알아보겠습니다! 📌✨

 

⏱️ Rate Limiter 패턴이란 무엇인가?

Rate Limiter 패턴은 지정된 시간 내 서비스가 처리할 수 있는 요청 수를 제한하는 방식입니다. 이를 통해 API가 갑작스러운 트래픽 증가로 인해 과부하 상태가 되거나 서비스 장애가 발생하지 않도록 보호할 수 있습니다. 이는 현대 클라우드 환경에서 API의 안정성과 가용성을 높이는 필수적인 방법입니다. 💡

📝 참고: 최신 클라우드 아키텍처에서는 자동 확장(autoscaling)이 효과적이지만, 이 글에서는 해당 주제를 다루지 않습니다.

 

🔍 Resilience4j의 두 가지 Rate Limiter 구현 방식

Resilience4j는 두 가지 Rate Limiter 구현 방식을 제공합니다:

  • AtomicRateLimiter (디폴트값)
  • SemaphoreBasedRateLimiter

1️⃣ SemaphoreBasedRateLimiter 방식 🔗

  • 자바의 java.util.concurrent.Semaphore를 사용하여 간단하게 제한을 설정합니다.
  • 각 사용자 스레드는 semaphore.tryAcquire()를 호출하여 허가(permission)를 얻고, 제한 시간이 갱신되면 semaphore.release()를 호출하여 다시 허가를 부여합니다.
  • 별도의 내부 스레드를 사용하여 권한을 관리하는 방식입니다.

2️⃣ AtomicRateLimiter 방식 (디폴트값) 🚧

  • 추가적인 스레드 관리가 필요 없습니다.
  • 각 호출 주기를 나노초 단위로 나누어 권한을 관리합니다.
  • 나노초 단위 주기마다 사용 가능한 허가(권한)를 리프레시합니다.

다음의 세 가지 주요 개념을 이해하면 AtomicRateLimiter를 쉽게 이해할 수 있습니다.

  • ActiveCycle: 마지막 호출에서 사용된 주기 번호
  • ActivePermissions: 마지막 호출 후 남은 허가(permissions) 수
  • NanosToWait: 마지막 호출에서 허가를 받기까지 기다려야 할 시간(나노초)

📌 RateLimiter 설정 시 고려해야 하는 Resilience4j 개념들

  • 주기(cycle)는 동일한 시간 단위로 나눠진다.
  • 권한이 부족하면 예약(reservation)을 수행하여 권한을 미리 확보한다.
  • 호출 허용 횟수(limitForPeriod), 갱신 주기(limitRefreshPeriod), 최대 대기 시간(timeoutDuration)을 설정할 수 있다.

 

🛠️ Rate Limiter 설정 방법 (bootstrap.yml 예시)

Resilience4j의 RateLimiter를 구성하는 방법은 다음과 같습니다:

📋 Listing 7.10 bootstrap.yml 설정 예시

resilience4j.ratelimiter:
  instances:
    licenseService:
      timeoutDuration: 1000ms      # 권한 대기 최대 시간 (기본: 5초)
      limitRefreshPeriod: 5000     # 권한 갱신 주기 (기본: 500ns)
      limitForPeriod: 5            # 한 주기 내 허용할 최대 호출 수 (기본: 50)

🔖 각 파라미터의 의미와 기본값

파라미터 설명 디폴트값
timeoutDuration 호출 허가를 기다리는 최대 시간 5초
limitRefreshPeriod 호출 권한 갱신 주기 500ns
limitForPeriod 한 주기 내 허용 가능한 호출 수 50개

 

🎯 서비스 메서드에 RateLimiter 적용 예시

아래는 Resilience4j에서 제공하는 다양한 패턴을 결합하여 사용한 예제입니다.

📋 실제 서비스 코드 (LicenseService.java)

@CircuitBreaker(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
@RateLimiter(name = "licenseService", fallbackMethod = "buildFallbackLicenseList")
@Retry(name = "retryLicenseService", fallbackMethod = "buildFallbackLicenseList")
@Bulkhead(name = "bulkheadLicenseService", fallbackMethod = "buildFallbackLicenseList")
public List<License> getLicensesByOrganization(String organizationId) throws TimeoutException {
    logger.debug("getLicensesByOrganization Correlation id: {}",
        UserContextHolder.getContext().getCorrelationId());
    randomlyRunLong();  // 장애 시뮬레이션 메서드
    return licenseRepository.findByOrganizationId(organizationId);
}

🔑 어노테이션 설명

  • @RateLimiter는 메서드 호출의 속도를 제한합니다.
  • 지정된 시간(limitRefreshPeriod) 동안 허용된 최대 호출 수(limitForPeriod)를 초과하면 추가 요청을 제한합니다.
  • 실패할 경우 지정된 폴백(fallbackMethod)이 호출됩니다.

 

📌 Bulkhead vs Rate Limiter – 무엇이 다를까요? 🤔

Bulkhead 패턴Rate Limiter 패턴은 비슷해 보이지만 중요한 차이가 있습니다:

패턴 핵심 목적 예시
Bulkhead 동시 호출(concurrent calls) 제한 "최대 10개까지 동시 호출 허용"
Rate Limiter 특정 시간 내 총 호출 수 제한 "1분에 최대 100개 호출 허용"

👉 서비스의 어떤 부분을 보호할 것인지에 따라 적합한 패턴을 선택하거나, 두 패턴을 결합하여 사용할 수도 있습니다.

 

🚩 RateLimiter를 선택할 때 유의점

  • 제한값(limitForPeriod)을 지나치게 낮게 설정하면 사용자 경험이 나빠질 수 있습니다.
  • 대기시간(timeoutDuration)을 너무 길게 설정하면 병목현상이 발생할 수 있습니다.
  • 운영환경의 실제 사용량을 기준으로 최적화된 설정을 찾는 것이 매우 중요합니다.

 

📎 참고 링크

더 자세한 Resilience4j Rate Limiter 옵션은 공식 문서를 참고하세요.

🔗 Resilience4j RateLimiter 공식 문서

 

🔥 결론 및 마무리

이제 Resilience4j의 Rate Limiter 패턴을 활용하여 API를 더욱 안정적이고 견고하게 운영할 수 있게 되었습니다. 🚀🎉

다음 글에서는 Resilience4j의 추가적인 활용법과 운영 노하우를 계속해서 소개하겠습니다. 감사합니다! 🙌✨

'Spring Microservice' 카테고리의 다른 글

ThreadLocal and Resilience4j  (1) 2025.03.16
Implementing the retry pattern  (0) 2025.03.16
99 퍼센타일(percentile)  (0) 2025.03.16
Implementing the bulkhead pattern  (1) 2025.03.16
Fallback processing  (0) 2025.03.16