Bucket4j를 이용한 Rate Limit 적용
이번에 적용한 gemini api는 일정 횟수 이상으로 요청하게 되면 요금이 발생한다.
물론 이번엔 상용화 서비스가 아니기 때문에 요금이 발생하지 않게 막아놨지만,
서비스에 적용을 할 때는 무분별하고 불필요한 요청들을 막기 위해 꼭 필요한 기능이라고 생각한다.
여러 방법이 있었지만, 그 중에서 마지막으로 고려한 방법은,
- Bucket4j
- resilience4j
이렇게 두 가지가 있었다.
MSA에 적합한 방법은 resilience4j인데, 이건 추가로 적용 방법을 바꿔서 기록하겠다.
이번엔 Bucket4j를 이용해서 적용해봤다.
이번 서비스에는 api 한 개에만 적용하면 됐기에, Service 레이어에 직접적으로 Bucket 선언과 동시에 메소드 내부에 요청 제한 체크를 해줬다.
생각보다 간단하다.
build.Gradle
// Bucket4j
implementation group: 'com.github.vladimir-bukhtoyarov', name: 'bucket4j-core', version: '7.6.0'
위와 같이 Bucket4j 의존성을 추가해준다.
@Service
@RequiredArgsConstructor
public class AIService {
// 초당 1회 요청을 허용하고, 5초마다 새로 토큰을 충전
private final Bucket bucket = Bucket.builder()
.addLimit(Bandwidth.classic(1, Refill.greedy(1, Duration.ofSeconds(5))))
.build();
@Transactional
public AIResponse YourMethod(User user) {
// Bucket을 사용한 요청 제한 체크
if (!bucket.tryConsume(1)) {
throw new IllegalStateException(ErrorCode.RATE_LIMIT_EXCEEDED.getMessage());
}
// 동작해야 할 함수들..
return new AIResponse(message);
}
}
요약하면 위처럼 Bucket4j와 관련된 코드는 굉장히 적다.
만약 나처럼 한 메소드에만 걸면 안되고 여러 곳에 적용해야 한다면,
따로 Config 클래스를 만들어서 여러 설정을 전역으로 적용해야 한다.
Bucket 선언부를 보면, 초당 1회 요청을 허용하고, 토큰을 5초마다 새로 충전하게 한다.
-> 이 말은 곧 5초에 1회의 Request만 허용한다는 뜻이다.
(우리는 테스트 시에, ai가 응답을 만드는 과정이 포함되어서 요청을 한 후 응답을 받기까지 거의 2초의 시간이 걸린다. 그렇기 때문에 요청 제한을 타이트하게 했다.)
그리고 해당 메소드를 타면, 토큰을 검사하여 에러를 발생시킨다.
이후 추가로 할 작업
- MSA 도입과 동시에 resilience4j로 방식 변경
- Config를 통해 여러 메소드에 전역 적용
해결!