대충 넘어가지 않는 습관을 위한 기록

Bucket4j를 이용한 Rate Limit 적용

uhyvn 2024. 11. 13. 13:07

 

이번에 적용한 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를 통해 여러 메소드에 전역 적용

 

 

해결!