Freely 서비스는 /chat
으로 사용자 질문과 함께 요청이 들어오면, Google의 Gemini REST API를 내부적으로 호출하여 대답을 생성하고 사용자에게 반환합니다.
특정 Gemini 모델의 요청이 실패할 경우(ex. 무료 요금제의 사용량 제한) 다른 Gemini 모델로 생성을 시도하는 기능을 구현하여, 가용성을 높이려던 중 문제가 발생하였습니다.
이 문서에서는 문제 발생 후로부터 어떻게 원인을 찾아나갔고, 더 나아가 어떻게 해결하였는지를 담고 있습니다.
가장 먼저 생각이 든 부분은 사용량 제한이였습니다. 가용성에 대한 부하 테스트를 하고자, jmeter와 같은 툴을 사용해 반복 요청을 서버에 보내고 있었습니다.
이는 내부적으로 Gemini API를 빠른 시간 내 반복적으로 호출하게 되었고, 아래 API 문서에서 언급하듯 속도 제한이 걸릴 수 있습니다.
따라서 잠깐씩 시간적인 여유를 두고 재실행을 해보았지만 문제는 해결되지 않았고, 다른 측면에서 생각을 해봐야 했습니다.
Rate limits | Gemini API | Google AI for Developers
시험삼아 Azure에 배포된 웹페이지에서 API 요청을 했으나 정상 작동하는 걸 확인할 수 있었습니다. 로컬 환경과 배포 환경에서 같은 Gemini API 키를 사용하기에, API 키가 밴당한 것은 아니라고 생각할 수 있습니다.
한편, API가 호출되는 IP에 기반해서 밴이 될 수도 있기 때문에, 로컬 개발 환경에서 Insomnia와 같은 REST API 도구를 사용하여, 직접 Gemini API를 호출해 보았습니다. 아래 사진과 같이 정상적으로 동작하는 걸 확인할 수 있습니다.
따라서 작성한 Spring 앱에 문제가 있다는 뜻이 되고, 소스 코드를 한번 살펴보았습니다.
client = RestClient.builder()
.baseUrl("<https://generativelanguage.googleapis.com/v1beta>")
.defaultHeader("Content-Type", "application/json")
.build();
final String[] MODEL_CODES = {
"models/gemini-2.0-flash",
"models/gemini-2.0-flash-lite",
"models/gemini-1.5-flash",
"models/gemini-1.5-pro",
};
private ChatResponse generateWith(String modelCode, String query) {
GeminiResposne body = client.post()
.uri("/{modelCode}:generateContent?key={apiKey}", modelCode, apiKey)
.body(new GeminiRequest(query))
.retrieve()
.body(GeminiResponse.class);
String text = body.candidates.getFirst().content.parts.getFirst().text;
return new ChatResponse(text);
}
위의 코드는 Gemini API를 호출하는 부분을 요약한 코드입니다.
REST API 호출 후 404 응답이 넘어오기 때문에, body
메서드가 호출되는 부분에 중단점을 설정해서 확인해야 합니다.
하지만 Spring 레퍼런스에 따르면, RestClient
는 4xx, 5xx 상태 코드가 발생하면 RestClientException
을 던지게 됩니다. 즉, 저 코드에서는 요청이나 응답 헤더를 확인하기 힘듭니다.
private ChatResponse generateWith(String modelCode, String query) {
GeminiResponse body = client.post()
.uri("/{modelCode}:generateContent?key={apiKey}", modelCode, apiKey)
.body(new GeminiRequest(query))
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
request.getURI();
})
.body(GeminiResponse.class);
String text = body.candidates.getFirst().content.parts.getFirst().text;
return new ChatResponse(text);
}