4 min read

Part 4. 비용 설계: 캐시, 배칭, 라우팅, 토큰 예산

LLM 비용은 모델 단가가 아니라 시스템 제어 방식에서 결정된다. 캐시, 배칭, 라우팅, 토큰 예산을 운영 관점으로 정리한다.

LLM 비용은 종종 "어떤 모델을 쓰느냐" 문제로 축소된다. 실제 운영에서는 모델 단가보다 시스템 구조가 총비용을 결정한다. 같은 모델을 써도 캐시 전략, 컨텍스트 길이, 라우팅 정책, 재시도 정책에 따라 월 비용이 2~5배까지 벌어진다. 그래서 비용 최적화는 구매 협상이 아니라 아키텍처 설계 문제다.

문제 제기

운영에서 반복되는 비용 실패 패턴은 대체로 비슷하다.

  • 컨텍스트를 무제한으로 붙여 "정확도"를 확보하려다 토큰 사용량이 폭증한다.
  • 작은 모델로 충분한 요청도 큰 모델로 일괄 처리해 단가가 높아진다.
  • 실패 재시도 시 동일한 긴 프롬프트를 반복 전송해 비용을 증폭한다.
  • 캐시가 있어도 키 설계가 약해 적중률이 낮고, 결과적으로 호출 수가 줄지 않는다.

실전 예시 A: 고객지원 FAQ 챗봇

전체 요청의 40%가 상위 30개 질문에 집중됐지만, 캐시 없이 매번 retrieval + generation을 수행했다. 응답 품질은 동일했는데도 월 비용이 지속 증가했다. 원인은 모델 문제가 아니라 반복 요청을 시스템이 인식하지 못한 것이다.

실전 예시 B: 내부 문서 분석 API

문서 요약 요청에서 원문 전체를 항상 첨부해 입력 토큰이 요청당 평균 20k를 넘었다. 실제로는 섹션 단위 분할과 요약 재조합으로 동일 정확도를 유지할 수 있었는데, 경계 설계 부재로 비용과 지연이 동시에 악화됐다.

핵심 개념

비용을 제어하려면 먼저 "요청 단위 원가(unit economics)"를 분해해야 한다.

항목질문대표 지표제어 레버
입력 토큰왜 길어졌는가input_tokens/request컨텍스트 압축, RAG top-k
출력 토큰왜 장문이 되었는가output_tokens/request응답 길이 정책, 포맷 강제
모델 단가왜 고가 모델이 선택됐는가model_mix_ratio라우팅 정책, 난이도 분류
호출 횟수왜 같은 요청을 반복 처리하는가calls/session캐시, 멱등 키, 재시도 정책
실패 비용실패가 왜 비용으로 누적되는가retry_token_cost실패 유형 분류, 재시도 제한

핵심은 "최저 단가 모델 사용"이 아니다. 품질(SLO)과 비용(SLA)을 동시에 만족하는 제어 루프를 설계해야 한다.

Mermaid diagram rendering...

실전 패턴

패턴 1: 요청별 토큰 예산(Token Budget) 강제

"좋은 답변을 위해 많이 넣자"는 정책은 운영에서 지속 불가능하다. 요청 목적별 최대 입력/출력 토큰을 명시하고, 초과 시 압축 또는 단계 처리로 전환해야 한다.

type BudgetPolicy = {
  inputMax: number;
  outputMax: number;
  overflowStrategy: "truncate" | "summarize" | "async";
};

const budgetByTask: Record<string, BudgetPolicy> = {
  faq_answer: { inputMax: 4000, outputMax: 600, overflowStrategy: "summarize" },
  policy_check: { inputMax: 2500, outputMax: 300, overflowStrategy: "truncate" },
  long_report: { inputMax: 8000, outputMax: 1200, overflowStrategy: "async" },
};

export function enforceBudget(task: string, inputTokens: number) {
  const policy = budgetByTask[task] ?? budgetByTask.faq_answer;
  if (inputTokens <= policy.inputMax) return { allowed: true, strategy: "none" as const };

  return {
    allowed: false,
    strategy: policy.overflowStrategy,
    overBy: inputTokens - policy.inputMax,
  };
}

운영 포인트:

  • 토큰 예산 위반 비율을 서비스별로 집계해 컨텍스트 설계 결함을 찾는다.
  • async로 전환된 요청은 사용자에게 예상 완료 시간을 명확히 표시한다.
  • 예산 정책은 주기적으로 재평가하되, 일시적 캠페인 트래픽에 따라 자동 확장하지 않는다.

패턴 2: 캐시 + 라우팅 결합 설계

캐시와 모델 라우팅을 따로 운영하면 효과가 제한된다. 라우팅 전 캐시 조회, 라우팅 후 응답 캐시 저장을 일관된 키 체계로 묶어야 한다.

type Route = "small" | "large";

function buildCacheKey(input: string, tenantId: string, policyVersion: string) {
  return hash(`${tenantId}:${policyVersion}:${normalize(input)}`);
}

export async function answer(input: string, tenantId: string) {
  const policyVersion = "v3";
  const key = buildCacheKey(input, tenantId, policyVersion);

  const hit = await cache.get(key);
  if (hit) return { source: "cache", ...hit };

  const route: Route = classifyDifficulty(input) === "hard" ? "large" : "small";
  const model = route === "large" ? largeModel : smallModel;

  const result = await model.generate(input);
  await cache.set(key, result, { ttlSec: route === "large" ? 3600 : 900 });

  return { source: route, ...result };
}

운영 포인트:

  • 테넌트/정책 버전을 캐시 키에 포함해 오염을 방지한다.
  • 고가 모델 결과는 TTL을 길게 가져가 비용 효율을 높인다.
  • 캐시 적중률만 보지 말고 "캐시로 절감한 토큰량"을 함께 추적한다.

패턴 3: 배치 처리와 비용 경보 자동화

실시간이 필요 없는 작업은 배치로 모아 처리하면 단가와 과금 변동성을 크게 줄일 수 있다.

# 5분 간격 배치 처리 예시
./jobs/llm-batch-runner.sh \
  --queue report_generation \
  --max-batch-size 120 \
  --max-total-input-tokens 300000 \
  --model small-first \
  --fallback large-on-fail

# 비용 경보: 시간당 비용이 베이스라인 대비 30% 초과 시 알림
./alerts/cost-guard.sh \
  --metric usd_per_hour \
  --window 1h \
  --baseline 14d \
  --threshold +30%

운영 포인트:

  • 배치 크기는 처리량뿐 아니라 실패 시 재처리 비용까지 고려해 결정한다.
  • 배치 재시도는 개별 작업 단위로 분리해 전체 배치 재실행을 피한다.
  • 비용 경보는 지연/품질 지표와 함께 보아야 오탐을 줄일 수 있다.

실패 사례/안티패턴

장애 시나리오: "폴백 폭주로 인한 비용 급등"

상황:

  • 1차 소형 모델의 일시 장애로 fallback 비율이 8%에서 64%로 급증했다.
  • fallback 모델은 1회 호출당 단가가 6배였고, 동시에 재시도도 활성화되어 시간당 비용이 4.3배 상승했다.
  • 사용자 품질은 오히려 낮아졌는데 비용만 증가했다.

탐지 절차:

  1. model_mix_ratio에서 large 모델 비율 급등 탐지
  2. retry_token_costusd_per_hour 동시 경보 발생
  3. trace에서 small timeout -> large fallback -> retry 반복 경로 확인

완화 절차:

  1. fallback 동시성 상한 적용(tenant별 cap)
  2. 비핵심 요청 유형을 즉시 축약 응답(degraded mode)으로 전환
  3. 재시도 정책을 TRANSIENT_INFRA에만 제한

회복 절차:

  1. fallback 활성화 조건에 비용 보호 게이트 추가
  2. 라우팅 정책에 "품질 대비 비용 상한" 규칙 반영
  3. 주간 리뷰에 cost per resolved request 지표를 추가

대표 안티패턴

  • 비용 문제를 모델 단가 협상으로만 접근
  • 품질 지표 없이 캐시/라우팅만 공격적으로 최적화
  • 재시도 토큰 비용을 관측하지 않는 운영
  • 테넌트 분리 없는 캐시 키로 데이터 혼선을 유발

체크리스트

  • 요청 유형별 입력/출력 토큰 예산이 코드로 정의되어 있는가?
  • 캐시 키에 테넌트/정책 버전/정규화 입력이 반영되는가?
  • 모델 라우팅 결과와 품질 지표를 함께 수집하는가?
  • fallback 비율 상한과 비용 보호 정책이 존재하는가?
  • 재시도 비용(retry_token_cost)을 별도 모니터링하는가?
  • 배치 작업에서 부분 실패 재처리 전략이 정의되어 있는가?
  • cost per resolved request를 핵심 운영 지표로 보고 있는가?

요약

LLM 비용 최적화는 "싼 모델 쓰기"가 아니라 제어면(control plane) 설계다. 토큰 예산, 캐시, 라우팅, 배치, 경보를 하나의 루프로 묶어야 품질과 비용을 동시에 지킬 수 있다. 프롬프트 튜닝은 비용 절감의 보조 수단일 뿐이며, 지속 가능한 절감은 시스템 정책에서 나온다.

다음 편 예고

다음 편에서는 보안(Security)을 다룬다. 프롬프트 인젝션, 데이터 유출, 권한 정책, 툴 샌드박싱을 단일 위협 모델로 연결해 설명한다. 특히 "모델이 안전해 보여도 시스템이 위험한" 상황을 어떻게 차단하는지, 정책 엔진과 실행 격리 관점에서 다룬다.


이전 편: Part 3. 신뢰성 설계: Retry, Timeout, Fallback, Circuit Breaker

다음 편: Part 5. 보안 설계: Prompt Injection, Data Leak, Policy Guard

댓글