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개가 충전됨