AI API 예산이 폭발했을 때: 멀티 프로바이더 라우팅 (Multi-Provider Routing)
요약
단일 AI API 프로바이더 의존으로 인한 비용 폭발과 장애 문제를 해결하기 위한 멀티 프로바이더 라우팅 전략을 다룹니다. 단순한 폴백 방식을 넘어 애플리케이션과 프로바이더 사이에 적응형 라우팅 레이어를 구축하는 설계 패턴을 제안합니다.
핵심 포인트
- 단일 프로바이더 의존은 비용 통제 불능과 단일 장애점 문제를 야기함
- 단순 try/except 방식의 폴백은 지연 시간과 코드 복잡도 문제를 유발함
- 애플리케이션과 API 사이에 독립적인 라우팅 레이어를 구축하는 것이 효율적임
- 비용 최적화와 신뢰성 확보를 위해 구성 가능한 라우팅 전략이 필요함
제 심장이 내려앉았던 바로 그 순간을 기억합니다. 어느 화요일 아침, 우리 AI API 프로바이더의 결제 대시보드를 열었을 때 3,200달러의 청구 금액이 저를 빤히 바라보고 있었습니다. 지난달 금액은 400달러였습니다. 주니어 개발자가 실수로 프로덕션(production) 환경에서 루프를 실행해 두었고, 그것이 중복된 프롬프트(prompt)로 엔드포인트(endpoint)를 계속해서 두들기고 있었던 것입니다.
그 고통은 실재했지만, 이는 저로 하여금 더 깊은 문제를 해결하도록 강제했습니다. 우리는 단일 AI 프로바이더(provider)에만 의존하고 있었고, 비용과 신뢰성이 완전히 통제 불능 상태였습니다.
진짜 문제
많은 팀과 마찬가지로, 우리도 가장 쉬운 방법인 단일 프로바이더로 시작했습니다. API는 직관적이었고, 문서화(documentation)도 괜찮았습니다. 하지만 단순한 챗봇(chatbot)에서 이메일 파싱(parsing), 문서 요약(summarizing), 코드 리뷰 생성(generating code reviews)과 같은 더 복잡한 자동화로 규모를 확장함에 따라, 단일 장애점(single point of failure)은 견딜 수 없는 수준이 되었습니다.
피크 시간대에는 속도 제한(Rate limits)이 우리를 괴롭히기 시작했습니다. 저렴한 쿼리(query)를 다른 모델로 라우팅(route)할 방법이 없었기 때문에 비용이 폭발했습니다. 그리고 만약 해당 프로바이더에 장애(outage)가 발생하면(3개월 동안 두 번 발생했습니다), 우리 제품은 완전히 마비되었습니다.
처음 시도했지만 실패했던 것들
저의 첫 번째 본능은 단순히 호출을 복제하는 것이었습니다. 프로바이더 A를 시도하고, 실패하면 프로바이더 B를 시도하는 방식이었죠. 저는 try/except 블록과 requests 라이브러리를 사용하여 간단한 Python 스크립트를 급하게 만들었습니다. 그것은... 약 이틀 동안은 작동했습니다.
# 순진한 폴백 (fallback) 방식 (이렇게 하지 마세요)
def query_ai(prompt):
try:
...
문제점: 각 예외(exception)가 몇 초의 지연 시간(latency)을 추가했고, 더 저렴한 프로바이더를 우선순위화할 방법이 없었으며, 어떤 호출이 실제로 성공하거나 실패했는지 추적할 수도 없었습니다. 게다가 세 번째 프로바이더를 추가하자 코드는 빠르게 스파게티 코드(spaghetti mess)로 변했습니다.
그 후에는 Celery와 작업 재시도(task retries)를 이용한 더 정교한 큐(queue) 기반 접근 방식을 시도했습니다. 그것은 상황을 더 악화시켰습니다. 다운스트림(downstream) API에 과부하를 주고, 더 엄격한 속도 제한(rate limits)에 걸렸으며, 필요하지 않은 컴퓨팅(compute) 비용을 지불하게 되었습니다.
결국 성공한 방법: 적응형 라우팅 레이어 (An Adaptive Routing Layer)
수많은 시행착오 끝에, 저는 다른 패턴으로 정착했습니다. 바로 애플리케이션 코드와 AI 프로바이더 (AI providers) 사이에 위치하는 라우팅 레이어 (routing layer)입니다. 대단히 화려한 것은 아닙니다. 본질적으로는 구성 가능한 전략 (configurable strategy)을 사용하여 어떤 프로바이더를 호출할지 선택하고, 성능을 추적하며, 폴백 (fallbacks)을 유연하게 처리하는 Python 클래스입니다.
약 80줄 정도의 핵심 아이디어는 다음과 같습니다:
import time
from typing import Callable, Dict, List
...
이 클래스는 로깅 (logging), 비동기 (async), 서킷 브레이커 (circuit breakers) 등이 구현되지 않아 프로덕션 환경에서 바로 사용할 수 있는 수준은 아니지만, 기반으로 삼을 수 있는 뼈대입니다. 핵심적인 통찰은 '어떤 프로바이더를 사용할지'에 대한 로직을 '어떻게 호출할지'에 대한 로직으로부터 분리(decoupling)하는 것입니다. 일단 이를 구현하고 나면, 가장 저렴한 것 우선 (cheapest-first), 가장 빠른 것 우선 (fastest-first), 프롬프트 길이 (prompt length) 기준, 또는 사용자 구독 등급 (user subscription level) 기준 등 온갖 종류의 전략을 추가할 수 있습니다.
또한 토큰 (tokens)을 추정하고 각 요청을 기록하는 간단한 비용 추적 (cost-tracking) 모듈을 추가했습니다. 그것만으로도 우리 팀의 비용을 절감할 수 있었습니다. 어떤 엔드포인트 (endpoints)가 가장 많은 비용을 발생시키는지 파악하고 그에 따라 라우팅 순서를 조정할 수 있었기 때문입니다.
실제 적용 사례
이를 사용하려면 API 호출을 감싸는(wrap) 프로바이더 함수들을 정의하면 됩니다. 예를 들어:
import openai
import anthropic
...
이제
- 라우팅 로직 (Routing logic)은 보기보다 단순합니다. 위의 클래스는 100줄 미만이지만, 실제 트래픽 패턴에 따라 우선순위 순서와 타임아웃 (timeout) 값을 미세 조정하는 데 상당한 시간을 소비하게 될 것입니다.
- 지연 시간 (Latency) 대 비용 (Cost) 사이의 트레이드오프 (tradeoff)는 실재합니다. 로컬 모델은 저렴하지만 CPU에서는 느립니다. 저희는 더 나은 지연 시간을 위해 로컬 추론 (inference)을 GPU 노드로 옮겼고, 이로 인해 인프라 비용이 추가되었습니다. 일부 유스케이스 (use cases)에서는 여전히 API 호출보다 저렴합니다.
- 모니터링 (monitoring)이 필요합니다. 통계가 없다면 눈을 감고 운전하는 것과 같습니다. 저희는 프로바이더 (provider) 성능과 사용자당 비용을 추적하기 위해 기존의 관측성 (observability) 스택과 통합했습니다.
- 모든 모델이 같은 언어를 사용하는 것은 아닙니다. Claude와 GPT는 포맷팅 (formatting)을 다르게 처리할 수 있습니다. 저희는 구조화된 출력 (structured outputs, JSON 파싱 등)을 위한 정규화 (normalisation) 레이어를 추가해야 했습니다.
- 프로바이더 API 변경은 발생합니다. Anthropic이 기존의 메시지 API를 폐기(deprecated)했을 때 저희는 큰 어려움을 겪었습니다. 라우팅 레이어 덕분에 하나의 프로바이더 함수만 업데이트하면 되었지만, 여전히 정신없는 상황이었습니다.
이 접근 방식을 사용하지 말아야 할 때
이 패턴은 복잡성을 더합니다. 예측 가능한 부하와 수용 가능한 비용을 가진 단일하고 안정적인 유스케이스가 있다면 굳이 시도하지 마세요. 또한, 엄격한 일관성(예: 재현성을 위해 항상 동일한 모델 버전을 사용해야 하는 경우)이 필요하다면 라우팅은 좋지 않은 아이디어입니다.
다음에 다시 한다면 다르게 할 점
임시방편적인 폴백 (fallback) 방식 대신, 첫날부터 더 단순한 설정 기반 (config-driven) 라우터를 사용했을 것입니다. 또한 속도 제한 (rate-limit) 인지 기능을 추가했을 것입니다. 현재의 라우터는 프로바이더가 스로틀링 (throttling)을 할 때 선제적으로 속도를 늦추지 않고, 그냥 실패한 뒤 다음으로 넘어갑니다. 적절한 서킷 브레이커 (circuit breaker) 패턴을 사용하는 것이 더 나을 것입니다.
그리고 운영 환경 (production)에서 루프가 돌아가게 내버려 두지는 않을 것입니다. 하지만 그건 아마 저만의 문제일지도 모르겠네요.
이 모든 경험을 통해 제가 배운 것은, 진정한 기술은 "최고의" AI 모델을 선택하는 것이 아니라, 실제 세계 API의 무질서함을 우아하게 처리하는 시스템을 구축하는 것이라는 점입니다.
그렇다면, 여러분의 설정은 어떤 모습인가요? 단일 프로바이더를 사용하시나요, 아니면 더 분산된 방식을 사용하시나요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기