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
의 메모리 효율 비교
bucket4j
의 Refill 방식이 메모리 사용량에 미치는 영향은 어떻게 될까요?
✅ 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개가 충전됨
'알아두면 좋은 개발 지식 > Java & Spring' 카테고리의 다른 글
양방향 엔티티의 재귀 호출 문제와 해결 방법 (1) | 2025.01.05 |
---|---|
선착순 쿠폰 발급 시스템 구현하기: Redis와 Kafka를 활용한 설계 (4) | 2024.09.26 |
Spring에서 동시성 이슈 해결 방법(MySQL, Redis 이용하기) (1) | 2024.09.25 |
[Spring] Spring Security '인증' 과정 (1) | 2024.09.13 |
[Spring Boot] Actuator로 서버 모니터링하기 (3) | 2024.09.07 |