Bucket4j를 이용해 API 요청 제한 정책 만들기

API 요청을 제어하려면 Rate Limiting(속도 제한) 이 필요합니다.


이 글에서는 API를 "1분 이내 30번 요청 가능하지만, 24시간 동안 최대 30번만 허용"하는 Bucket4j의 정책을 설정하는 방법을 다룹니다.

*Bucket4j을 도입하는 방법을 설명하는 것은 아닙니다.

🧐 Rate Limiting이란?

Rate Limiting(속도 제한)은 특정 시간 동안 허용할 요청 수를 제한하는 기술입니다.
예를 들어:

  • "1분에 60개의 요청 가능" (API 남용 방지)
  • "24시간 동안 100개의 요청 가능" (과도한 사용 제한)

이러한 제한을 적용하면 서버 부하를 방지하고, 공정한 API 사용을 보장할 수 있습니다.


🛠 Bucket4j로 정책 설정하기

Bucket4j토큰 버킷(Token Bucket) 알고리즘을 기반으로 속도 제한을 구현하는 Java 라이브러리입니다.

원하는 정책
✅ 1분 이내에 50번 요청 가능
✅ 24시간 동안 최대 50번 요청 가능 (초과 불가)

 

이를 위해 Bandwidth.classic()을 사용하여 버킷을 설정할 수 있습니다.

 

 

우선 최종 정책 설정 값부터 보여 드리겠습니다.

🔹 구현 코드(정책 설정 값)

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Refill;

import java.time.Duration;

public class RatePlan {

    public static final int MAX_TOCKEN_COUNT = 30;

    /**
     * 24시간 동안 최대 50번 요청 가능
     * 1분 안에 50번 요청 가능하지만, 24시간 동안 50개 이상 충전되지 않음
     */
    public static Bandwidth resolvePlan() {
        return Bandwidth.classic(
            MAX_TOCKEN_COUNT,
            Refill.intervally(MAX_TOCKEN_COUNT, Duration.ofDays(1)) // 24시간마다 50개 충전
        );
    }
}

Refill.intervally(50, Duration.ofDays(1))의 동작 방식

Refill.intervally(50, Duration.ofDays(1))24시간마다 한 번에 50개를 충전하는 방식입니다.

최대 50개 토큰을 한 번에 사용 가능
토큰을 다 쓰면 24시간 동안 충전되지 않음
24시간이 지나면 다시 50개로 리셋

 

예제 시나리오

시간 남은 토큰 개수 설명
00:00 30개 초기 상태 (최대 30번 요청 가능)
00:05 28개 2개 사용
04:00 25개 추가 3개 사용
23:59 25개 하루가 지나기 전까지 더 이상 충전되지 않음
24:00 30개 24시간이 지나면 한 번에 30개 충전

 

💡 포인트

  • 개별적으로 토큰이 충전되는 것이 아니라, 24시간이 지나야 한꺼번에 30개가 채워짐
  • 즉, "2개를 사용했으니 24시간 후 2개만 충전" 같은 동작이 아님 주의

🚨 intervally vs greedy 차이

bucket4j에는 두 가지 주요 토큰 충전 방식이 있습니다.

방식 설명 예제
intervally 특정 시간이 지나면 한 번에 충전됨 Refill.intervally(50, Duration.ofDays(1)) (매일 50개 한 번에 충전)
greedy 일정한 간격마다 서서히 충전됨 Refill.greedy(30, Duration.ofDays(1)) (매초마다 (50 / 86400초 ≈ 0.0005개) 충전)
  • intervally(n, duration): 정해진 시간이 지나면 한 번에 n개 충전
  • greedy(n, duration): duration 동안 n개가 나눠서 서서히 충전

 

🚀 intervally vs greedy의 메모리 효율 비교

bucket4jRefill 방식이 메모리 사용량에 미치는 영향은 어떻게 될까요?


✅ 1. intervally(n, duration)의 메모리 사용

✔️ 특징
  • 일정 시간이 지나면 한 번에 n개의 토큰이 충전됨
  • 마지막 충전 시점만 저장하면 되므로 메모리 사용량이 적음
  • 충전 로직이 간단하므로 CPU 부담도 낮음
🛠 메모리 사용 방식
  • Bucket마지막 충전된 시간만 기억하면 됨
  • 매 충전 주기가 도래하면 버킷에 n개의 토큰을 즉시 추가
🏆 메모리 효율성: ✅  좋음
  • 매 순간 충전 상태를 기록할 필요 없이, 한 번의 갱신으로 해결
  • 장기적으로 보면 메모리 사용량이 적음

✅ 2. greedy(n, duration)의 메모리 사용

✔️ 특징
  • 토큰이 조금씩 서서히 충전되므로 매 순간 계산이 필요함
  • 충전 속도가 부드럽지만, 계산이 자주 일어나므로 추가적인 메모리와 CPU 연산이 필요
🛠 메모리 사용 방식
  • Bucket현재 시점까지 얼마나 충전되었는지를 계속 계산해야 함
  • 매 요청마다 남은 토큰을 계산해야 하므로, 시간과 상태를 더 자주 업데이트해야 함
메모리 효율성: 상대적으로 낮음
  • 지속적으로 충전량을 계산해야 해서 메모리 사용량이 더 높아짐
  • 특히 많은 요청이 오가는 시스템에서는 intervally보다 더 많은 연산과 메모리를 사용

🔥 결론: intervally 을 선택

방식 메모리 사용량 연산 부담 적합한 상황
intervally(n, duration) ✅ 낮음 ✅ 낮음 명확한 충전 간격이 필요한 경우 (예: 하루 100개 요청 제한)
greedy(n, duration) ❌ 높음 ❌ 높음 지속적인 요청을 부드럽게 처리하고 싶은 경우 (예: 초당 제한이 필요한 API)

 

💡 선택 기준

  • 메모리 절약이 중요하고 충전 간격이 명확해야 한다면 → intervally
  • 부드럽게 토큰을 충전하고 싶지만, 약간의 메모리 사용 증가를 감수할 수 있다면 → greedy

즉, 필요한 기능의 요구 사항과 메모리 효율을 고려하여 intervally을 선택 했습니다.


만들어진 24시간 제한 정책

"1분 내에 50번 요청 가능하지만, 24시간 동안 최대 50번만 허용" 하는 정책을 만들었습니다.

 

최대 50개 토큰 보유 가능
1분 내에 50개를 전부 사용 가능
토큰을 다 써도 개별적으로 충전되지 않음
첫 요청으로부터 24시간이 지나야 다시 50개가 충전됨