Provider Drift: 기본 라우팅이 LLM 비용을 키우는 방식
목차
Prompt caching을 켰고 hit 카운터도 가끔 올라가는데, 청구액은 거의 줄지 않았습니다. prompt 구조를 의심하기 전에 대시보드에 표시되지 않는 정보부터 확인해야 합니다. 각 요청을 실제로 처리한 upstream이 어디인지 보십시오.
멀티 Provider Gateway는 하나의 모델을 여러 upstream provider에 분산하고 요청마다 하나를 선택합니다. Prompt cache는 provider별로 나뉘며, 같은 provider 안에서도 node별로 분리되는 경우가 많습니다. 따라서 두 번째 요청이 첫 번째와 다른 upstream에 도착하면 prompt가 단 한 byte도 바뀌지 않았어도 cache miss가 발생합니다. 이를 provider drift라고 합니다. token당 과금 모델에서는 이 현상으로 비용이 조용히 불어납니다.
TL;DR
- 한 멀티 Provider Gateway를 기본 라우팅 설정으로 사용했더니, 동일한 요청 20건이 upstream 9곳으로 분산됐고 prompt cache hit는 20건 중 4건에 그쳤습니다.
- 단일 backend Gateway에서는 같은 workload로 20건 중 19건이 적중했습니다. drift가 발생한 실행의 비용은 약 3.9x 더 높았습니다($0.0102 vs $0.0026).
- 측정한 DeepSeek 계열 모델에서는 cache miss 1건의 비용이 hit보다 약 4x 높았습니다(호출당 median $0.00062 vs $0.00015).
- drift는 모델별로 다릅니다. 같은 Gateway에서도 GPT 계열 모델은 20건 모두 하나의 upstream으로 라우팅돼 20건 중 19건이 적중했습니다.
이 현상이 발생하는 두 가지 조건
사용자가 잘못 설정해서 생기는 문제가 아닙니다. 초기 설정 그대로 사용하면 발생합니다.
- 기본 auto routing. 특정 upstream을 고정하지 않고 모델에 요청을 보내므로 Gateway가 호출마다 upstream을 선택합니다.
- 기본 provider 정렬 = “default (balanced)”. Gateway가 하나의 upstream을 계속 사용하지 않고, 사용 가능한 upstream 사이에서 load balancing합니다.
둘 다 초기 기본값입니다. 아무 설정도 건드리지 않아도 drift가 발생합니다. 이를 피하려면 오히려 설정을 변경해야 합니다.
동일한 요청 20건을 보내면 생기는 일
널리 쓰이는 한 멀티 Provider Gateway에 동일한 약 8K-token prefix를 연속으로 20번 보냈습니다. 위의 기본 설정을 사용했고, 매번 upstream이 직접 보고하는 provider 및 cache field도 요청했습니다. disk cache를 사용하는 DeepSeek 계열 모델의 결과는 다음과 같습니다.
- 20건의 호출을 서로 다른 upstream 9곳에서 처리했습니다.
N***a,S***w,M***h,D***a,A***L,P***l,S***e,V***e,A***d. - Cache hit rate: 4/20 (20%). 이미 prefix를 cache한 upstream에 우연히 도착한 호출만 적중했습니다.
동일한 workload를 단일 backend Gateway에서 20번 실행했습니다. 모델 하나와 upstream 하나만 사용하고 balancing은 적용하지 않았습니다. hit rate는 **19/20 (95%)**였습니다. 모델, prompt, 호출 횟수는 모두 같았습니다. 달라진 것은 routing drift의 발생 여부뿐입니다.
반면 동일한 멀티 Provider Gateway에서 GPT 계열 모델은 20건 모두 하나의 upstream(A***e)으로 라우팅됐고, 19/20이 적중했습니다. drift는 모든 모델에 똑같이 발생하지 않습니다. Gateway가 여러 upstream으로 분산하는 모델에만 영향을 미칩니다. 이번 실행에서는 DeepSeek 계열 모델이 그 대상이었습니다.
결론 A: 예상 비용과 실제 비용의 차이
drift가 발생한 모델의 호출당 비용은 cache 적중 여부에 따라 명확히 갈렸습니다.
| 호출 유형 | 호출당 median 비용 |
|---|---|
| cache hit | ~$0.00015 |
| cache miss | ~$0.00062 |
이 모델에서는 miss가 hit보다 약 4x 비쌉니다. 공개된 raw input token 가격의 격차는 약 50x로 더 큽니다. 이를 20건 전체로 합산하면 다음과 같습니다.
| 시나리오 | hit rate | 동일한 호출 20건의 비용 |
|---|---|---|
| 예상(cache 접근 가능) | 95% | $0.0026 |
| 실제(기본 설정에서 drift 발생) | 20% | $0.0102 |
같은 모델, 같은 prompt, 같은 요청 20건이었습니다. Provider drift로 실행 비용이 약 3.9x 증가했습니다. caching은 계속 “활성화”된 상태였지만, routing layer 때문에 대부분의 token이 miss 요율로 과금됐습니다. 크고 안정적인 prefix를 온종일 반복하는 production endpoint라면 이 차이가 input 비용의 대부분을 차지합니다.
결론 B: cache가 없으면 latency 개선도 없다
Caching은 비용만 줄이는 기능이 아닙니다. prefill이 warm 상태면 첫 token이 더 빨리 반환됩니다. drift로 cache를 사용하지 못하면 이 속도 향상도 사라집니다. 동일한 호출을 반복하며 time-to-first-token(TTFT)을 측정했습니다.
GPT 계열 모델(일관되게 하나의 upstream으로 라우팅돼 cache 접근 가능):
| 호출 | TTFT |
|---|---|
| 1번째(cold, miss) | ~1760 ms |
| 이후 호출(warm, hit) | ~1130 ms |
Caching을 사용하면 첫 token이 약 36% 빨라집니다. 결과도 안정적이어서 warm 상태의 모든 호출이 좁은 범위에 모였습니다.
DeepSeek 계열 모델(기본 설정에서 drift가 발생해 cache 접근이 거의 불가능):
- 동일한 호출 10건을 반복한 결과 cache hit: 0.
- 호출마다 TTFT가 ~1000 ms에서 ~4500 ms까지 흔들렸고, 간헐적으로 빈 response도 반환됐습니다.
거의 모든 요청이 새로운 upstream으로 전달되기 때문에 계속 cold prefill latency가 발생합니다. 응답한 provider에 따른 편차도 그대로 영향을 줍니다. GPT 모델은 접근 가능한 cache 덕분에 TTFT가 36% 개선됐습니다. drift가 발생한 모델은 개선이 전혀 없었고, 가장 빠른 호출과 가장 느린 호출의 차이도 4.5x에 달했습니다.
5분 안에 직접 설정 점검하기
이 수치든 다른 사람이 제시한 수치든 그대로 믿지 마십시오. 동일한 긴 prefix를 여러 번 보내고 두 field를 확인하면 됩니다. domain은 hardcode하지 않았습니다. env var를 사용해 직접 운영하는 Gateway를 지정하십시오.
import os, uuid
from openai import OpenAI
client = OpenAI(api_key=os.environ["GW_KEY"], base_url=os.environ["GW_BASE"])
SYS = f"[probe {uuid.uuid4().hex}]\n\n" + ("You are a support assistant. " * 300)
seen, hits = {}, 0
for i in range(20):
r = client.chat.completions.create(
model=os.environ["GW_MODEL"], max_tokens=16,
messages=[{"role": "system", "content": SYS},
{"role": "user", "content": f"q{i}"}],
extra_body={"usage": {"include": True}})
d = r.model_dump()
det = r.usage.prompt_tokens_details
cached = (getattr(det, "cached_tokens", 0) or 0) if det else 0
seen[d.get("provider")] = seen.get(d.get("provider"), 0) + 1 # populated when exposed
hits += 1 if cached else 0
print(f"hit rate {hits}/20; upstreams seen: {len(seen)}")
같은 모델에 두 개 이상의 upstream이 사용됐다면 drift가 발생한 것입니다. hit rate가 prompt 안정성에 비해 크게 낮다면 그만큼 추가 비용을 내고 있습니다. 더 자세한 방법은 LLM Gateway가 cache 정보를 속이고 있는지 확인하는 방법에서 다룹니다.
확인할 사항
drift를 해결하려면 구조를 바꿔야 합니다. 특정 모델을 일관된 backend로 라우팅해야 다음 요청에서도 warm cache에 접근할 수 있습니다. 호출마다 prefix를 한 번도 본 적 없는 새로운 upstream으로 load balancing해서는 안 됩니다. Gateway를 평가할 때 같은 prefix를 20번 보내고 사용된 upstream 수를 세십시오. 원하는 결과는 하나입니다. 9개라면 추가 비용을 내고 있는 것입니다.
단, prompt caching은 어디서나 best-effort 방식입니다. disk cache를 사용하는 모델은 backend가 하나여도 idle 시간이 길어지면 hit rate가 낮아집니다. drift를 제거한다고 cache가 무한히 유지되는 것은 아닙니다. 다만 사용자가 선택하지도 않았고 관찰할 수도 없는, 가장 크고 낭비가 심한 miss 원인을 제거할 수 있습니다.
마무리
“Prompt caching을 지원한다”와 “내 요청이 cache에 접근할 수 있다”는 서로 다른 주장입니다. 하나의 모델을 계속 바뀌는 여러 upstream에 분산하는 Gateway도 cache 지원을 사실대로 표기할 수 있습니다. 하지만 실제 결과는 20%의 hit rate, 약 4x의 청구액, 최대 4.5x까지 흔들리는 첫 token latency일 수 있습니다. 확인해야 할 수치는 caching 지원 여부가 아닙니다. 직접 측정한 hit rate와 동일한 요청이 거치는 upstream 수입니다. probe를 실행하고 데이터로 판단하십시오.
전체 점검 방법은 LLM Gateway가 cache 정보를 속이고 있는지 확인하는 방법을 참고하십시오. cache가 필요한 이유는 KV Cache와 TTL의 동작 원리에서 설명합니다.
FAQ
내 설정이 잘못돼서 발생하는 문제인가요? 아닙니다. auto routing과 provider 정렬이 “default (balanced)“로 설정된 초기 기본값에서 발생합니다. drift를 피하려면 upstream을 직접 고정해야 합니다. 반대가 아닙니다.
upstream 하나를 고정하면 해결되나요? provider 간 drift는 사라집니다. 하지만 하나의 upstream도 prefix affinity 없이 여러 replica를 운영하는 경우가 많아 hit와 miss가 번갈아 발생할 수 있습니다. 고정했다고 가정하지 말고 변경 후 직접 측정하십시오.
GPT 계열 모델에서는 왜 drift가 발생하지 않았나요? 이번 실행에서는 Gateway가 우연히 하나의 upstream으로만 라우팅했습니다. drift는 모델별로 발생하며, Gateway가 load balancing하는 사용 가능 upstream 수에 따라 달라집니다. 모든 모델에 동일하게 나타나지 않습니다.
비용 차이가 정말 약 4x인가요? 측정한 호출당 총비용에서는 miss가 hit보다 약 4x 비쌌습니다. 이 모델 계열에 공개된 raw input-token 가격을 기준으로 하면 hit와 miss의 격차는 50x에 가깝습니다. 어느 쪽이든 예상했던 hit가 miss로 바뀌는 것이 비용 증가의 핵심입니다.
어떤 metric 하나를 모니터링해야 하나요? 시간에 따른 모델별 cache hit rate를 확인하고, 모델별로 사용된 서로 다른 upstream 수도 함께 보십시오. hit rate가 떨어지거나 upstream 수가 늘어나면 실질 token 비용도 증가한 것입니다.