안녕하세요, 뤼튼테크놀노지스의 Tech Lead 한승우(닉네임 쌤)입니다. 자고 일어나면 새로운 AI 기술이 등장하는 요즘, 뤼튼 팀에서 사용자들이 더 쓰기 편한 AI Native 서비스를 개발하고 운영하는 역할을 맡고 있습니다. 이번 글을 시작으로 앞으로 뤼튼 엔지니어 팀이 어떤 방식으로 서비스를 고도화하고, 새로운 AI 기술을 적용하는지에 대한 글을 작성해 공유할 예정이니 많은 관심 부탁드립니다. 오늘은 그 첫 번째로 ‘지수 백오프(Exponential Backoff, #1) 알고리즘’을 활용한 외부 API Rate Limit(#2) 대응 방법에 관해 설명해 보겠습니다.
#1: 재전송 지연 시간을 지수적으로 늘려 나가는 방식
#2: 서버가 특정 임계치까지만 클라이언트의 요청을 허용하는 정책
뤼튼의 엔지니어 팀은 서비스 고도화를 위해 다양한 문제를 해결하고 있습니다. 그중에서도 대규모 언어 모델(Large Language Models, LLM)의 Rate Limit는 뤼튼 운영 초기부터 지금까지 계속되는 문제입니다. 특히 사용자가 원하는 최고의 결과물을 생성하기 위해 국내외 유명 테크 기업들의 LLM을 사용하고 있는데, 기업들마다 Rate Limit 제공 조건이 모두 달라서 어려움을 겪고 있습니다.
API 제공자는 원활한 리소스 관리를 위해 Rate Limit에 다양한 조건을 설정하는데, 보통 일정 시간 동안 API를 사용할 수 있는 횟수를 제한하는 것이 일반적입니다. 그래서 사용자가 정해진 시간 안에 API를 사용하는 횟수를 초과하면 일정 시간 동안 해당 API를 사용할 수 없게 됩니다. 뤼튼은 현재 Azure Open AI를 이용해 GPT-4 모델을 사용하고 있는데, 이 API의 경우 Rate Limit은 RPM(Requests Per Minute)이 ‘18’입니다. 즉, 1분에 18개의 요청을 보내면 1분 이내에 더 이상 요청을 보내도 응답을 받을 수 없는 상태가 됩니다.
항상 사용자 입장에서 생각하는 뤼튼 개발자들은 이를 용납할 수가 없었습니다. 특히 날이 갈수록 뤼튼 사용자는 가파르게 상승하고 있기 때문에 빠르게 해결해야 했습니다. 먼저 Rate Limit 문제를 ‘식당에 많은 손님이 오고 있고, 이를 만들 많은 요리사도 있는데, 정작 조리가 가능한 화구가 5개 밖에 없다.’로 정의했습니다. 그리고 ‘손님이 식당에 방문했을 때 자리가 없다고 돌려보내기보다는(서비스 접속 오류 혹은 에러) 자리가 새로 생길 때까지 잠시 대기시키고 빠르게 손님을 입장시키자’라고 결론지었습니다.
문제를 정의했으니 이제 빠르게 새로운 자리를 만들고, 손님을 입장시킬 방법을 찾아야 했습니다. 그렇게 밤낮없이 개발자들이 모여 좋은 방법을 고민했고, 여러 해결책을 만들었습니다. 그중 하나가 이번에 소개할 ‘지수 백오프 알고리즘을 이용한 Rate Limit 완화’입니다.
‘지수 백오프’는 재시도 알고리즘 중 하나입니다. 이 알고리즘은 일시적인 오류로 실패한 요청을 재시도할 때 사용됩니다. 기본적으로, 알고리즘은 처음 요청이 실패하면 일정 시간 후에 동일한 요청을 다시 시도합니다. 하지만 동일한 요청을 지속적으로 시도하는 것은 서버나 네트워크 내부에서 문제를 야기할 수 있습니다. 따라서 이 알고리즘의 핵심은 이전 시도의 실패 횟수를 고려하여 요청을 재시도하는 시간을 늘리는 것입니다.
이 알고리즘은 지수적으로 거리나 시간을 늘리기 때문에 ‘지수 백오프 알고리즘’이라고 불립니다. 이는 처음 재시도에서는 짧은 시간(예: 1초)을 기다리고, 실패하면 재시도를 시도하되, 그 시간을 두 배(예: 2초)로 늘립니다. 이후 실패 시마다 이전 시간의 2배씩 늘려가며 요청을 재시도하다가 일정 시간 이상이 지난 경우 재시도를 중단합니다.
아래는 Python 기반의 지수 백오프 알고리즘의 예시 코드입니다.
import time
import random
def api_call():
# do some API call here
# and return the result or raise an exception on failure
# for the purposes of this example, we'll just raise an exception
raise Exception("Error: API call failed")
def exponential_backoff(max_attempts=5, max_wait=60):
attempts = 0
wait_time = 1
while attempts <= max_attempts:
try:
# attempt API call
result = api_call()
return result
except Exception as e:
# log error
print(e)
# if this is the last attempt, give up
if attempts == max_attempts:
raise
# otherwise, increase the attempt count and wait some time before retrying
attempts += 1
wait_time = random.uniform(0, wait_time * 2)
print(f"Attempt {attempts} failed, waiting {wait_time} seconds before retrying...")
time.sleep(wait_time)
# if we have waited too long, give up
if wait_time > max_wait:
raise Exception("Exceeded maximum wait time, giving up")
위 예시 코드는 api_call 함수를 가정합니다. 이 함수는 API를 호출하고 결과를 반환하는 대신, Exception을 일으킵니다. api_call 함수는 실패하는 경우를 재현하기 위해 이렇게 작성되었습니다. exponential_backoff 함수는 max_attempts와 max_wait 매개 변수를 사용합니다. max_attempts는 재시도할 최대 횟수를 지정하는 데 사용되며, max_wait는 재시도를 중지하기 전에 기다릴 최대 시간(초)을 지정합니다.
이 함수는 처음 요청을 시도하고, 실패한 경우 이전에 대기한 시간을 두 배로 늘립니다. 실패한 경우 wait_time 값을 두 배로 늘리는 지수적 거리 계산을 수행하며, 재시도하기 전에 해당 시간만큼 대기합니다. 매번 대기 시간이 늘어남에 따라 재시도 간격이 더 균등하게 분산됩니다. 이 대기 시간 계산을 통해, 정확한 값을 활용해 API 호출을 요청하는 것보다 지수적으로 거리를 둠으로써 서버나 네트워크 오버로드 문제를 방지하면서도 재시도를 효과적으로 처리할 수 있습니다.
지수 백오프의 핵심은 ‘요청을 얼마나 대기시킬 것인가?’입니다. 식당을 기다리던 사람들이 하염없이 1시간, 2시간 기다리면 안 되기 때문입니다. 그래서 직접 지수 백오프를 실행할 횟수를 얼마나 둘건지 값을 변경하면서 API의 평균 응답시간, Rate Limit을 고려해 최적화된 재시도 횟수를 찾아가는 것이 핵심입니다.
참고로 직접 코드를 짜지 않아도, 지수 백오프를 구현해 둔 오픈소스가 많습니다. 뤼튼에서 가장 많이 참고한 건 깃헙에 올라온 이 오픈소스입니다.
뤼튼 개발 팀은 2022년 8월에 뤼튼 베타 테스트 출시를 앞두고 진행한 부하테스트 과정을 통해 해당 문제를 인지하고 있었습니다. 위 이미지(왼쪽)는 실제로 테스트를 위해 부하테스트를 도입했었고, 그 과정에서 기록해 놓았던 API 부하 테스트 결과입니다. 지수 백오프 미적용 시 총 180번의 호출 중 73회가 성공하고, 107회가 실패했습니다. 지수 백오프 적용 후에는 같은 180번의 호출 중 143회가 성공하고, 36회가 실패했습니다. 이러한 부하테스트를 통해 저희는 지수 백오프가 API 호출 성공에 있어 큰 효과를 내는 것을 확인할 수 있었고, 이후 여러 뤼튼 서비스에 적용했습니다.
뤼튼은 쉽고 똑똑한 AI 플랫폼인 뤼튼뿐만 아니라 ‘뤼튼 도큐먼트’라는 AI를 통해 쉽게 문서 초안을 작성하는 서비스를 운영하고 있습니다. 이 서비스의 경우, 사용자의 요청에 최고의 결과물을 생성하기 위해 한 번에 수십 개의 API 요청이 진행되고 있습니다. 초당 60회 이상의 API 호출이 필요한 서비스에 Rate Limit RPM이 ‘18’인 API가 있다면 서비스 운영에 큰 문제가 생깁니다. 다행히 뤼튼 도큐먼트에서도 지수 백오프 알고리즘을 잘 적용할 수 있었고, 평균 API 응답 성공률도 10%에서 80% 정도로 늘어났습니다. 이러한 성공 사례들에 힘입어 뤼튼 개발팀은 이 알고리즘을 활용해 동시에 수백 개의 API 요청에 대응할 수 있게 됐습니다.
외부 API를 활용하는 많은 스타트업 서비스가 Rate Limit로 인한 장벽을 만나보셨을 겁니다. 사실 제한 자체를 없애는 것이 가장 좋고 이상적인 해결 방법이지만, 조건이 까다롭기 때문에 쉽지 않은 방법입니다. 또한 생성 AI 기술이 빠르게 발전하면서 API 형태로 제공하는 LLM이 늘어나고 있기 때문에 일일이 해제하는 건 시간이 오래 걸리는 일입니다. 그래서 이러한 문제를 빠르게 해결할 수 있는 방법 중 하나로 지수 백오프 알고리즘을 소개했습니다.
물론 지수 백오프 알고리즘 하나로 모든 문제가 해결되지는 않습니다. 그렇지만 급변하는 생성 AI 시대를 앞서나가기 위해서는 사용자 중심의 사고로 문제를 빠르게 해결할 필요가 있습니다. 지수 백오프 알고리즘은 Rate Limit 문제가 모두 해결되지는 않겠지만, 급한 상황에서 유용하게 쓸 수 있는 방법입니다. 이 글을 통해 비슷한 문제를 고민하는 다른 개발자들에게 조금이나마 도움이 되기를 바랍니다. 앞으로 또 다른 글을 통해 뤼튼이 맞닥뜨린 여러 문제를 어떻게 해결하는지 자주 소개하겠습니다. 감사합니다.