당신의 LLM 래퍼가 돈을 새고 있다: 시맨틱 캐싱의 아키텍처
Source: Dev.to
문제: 지갑을 태우는 LLM 호출
GenAI 기능을 급히 배포하면서 대부분의 엔지니어링 팀이 겪는 세 가지 일반적인 장애물은 504 Gateway Timeout, Hallucination Loop, 그리고 가장 고통스러운 지갑 소모입니다.
프로덕션 로그에는 스타트업들이 LLM API를 일반 REST 엔드포인트처럼 취급하고 캐싱을 잘못 구현해 매달 수천 달러를 OpenAI 청구서에 쓰고 있다는 사실이 나타납니다.
대형 언어 모델을 사용할 때 단순 key‑value 캐싱은 효과가 없습니다. 사용자가 문장을 다르게 표현해도 같은 질의에 대해 두 번 비용을 지불하지 않도록 Semantic Caching이 필요합니다.
나쁜 패턴: 정확히 일치하는 캐싱
대부분의 백엔드 엔지니어는 사용자의 프롬프트를 해시하고 정확히 같은 문자열이 있으면 캐시된 응답을 반환하는 간단한 Redis 체크로 API 호출을 감싸는 것부터 시작합니다.
# The Naive Approach
def get_ai_response(user_query, mock_llm, cache):
# PROBLEM: Only checks exact matches.
if user_query in cache:
return cache[user_query]['response']
response = mock_llm.generate(user_query)
cache[user_query] = response
return response
왜 프로덕션에서 실패하는가
인간 언어는 일관되지 않습니다:
- 사용자 A: “What is your pricing?”
- 사용자 B: “How much does it cost?”
- 사용자 C: “Price list please”
키‑밸류 스토어는 이를 세 개의 서로 다른 키로 취급해 세 번의 LLM 호출을 발생시킵니다. 트래픽이 많은 앱에서는 이 중복이 전체 토큰 사용량의 **40‑60 %**를 차지할 수 있습니다.
비용 예시
| 하루 요청 수 | 의미 기반 캐싱 미사용 | 캐시 적중률 50 % 적용 |
|---|---|---|
| 1,000 | $150 / month* | $75 / month |
*요청당 1,500 입력 토큰을 $0.005 per 1K 토큰 기준으로 계산.
임베딩 비용 부가: ≈ $2 / month (text-embedding-3-small을 $0.00002 per 1K 토큰 기준).
순절감액: $73 / month → $876 / year.
좋은 패턴: 의미 기반 캐싱
lexical(문자 그대로) 일치에서 semantic(의미) 유사성으로 이동하려면 벡터 임베딩을 사용합니다.
아키텍처
- 임베드 – 저렴한 모델(예:
text-embedding-3-small)을 사용해 들어오는 사용자 질의를 벡터로 변환. - 검색 – 이전 질의들의 저장된 벡터와 비교.
- 임계값 – 코사인 유사도를 계산하고, 점수가 설정한 임계값(예: 0.9)을 초과하면 캐시된 응답을 반환.
구현
import math
from openai import OpenAI
# 1. Cosine similarity
def cosine_similarity(v1, v2):
dot_product = sum(a * b for a, b in zip(v1, v2))
norm_a = math.sqrt(sum(a * a for a in v1))
norm_b = math.sqrt(sum(b * b for b in v2))
return dot_product / (norm_a * norm_b)
def get_ai_response_semantic(user_query, llm, cache, client):
# 2. Embed the current query
embed_resp = client.embeddings.create(
model="text-embedding-3-small",
input=user_query
)
query_embedding = embed_resp.data[0].embedding
# 3. Threshold (tune as needed)
threshold = 0.9
best_sim = -1
best_response = None
# 4. Linear search (for learning; replace with ANN DB in prod)
for cached_query, data in cache.items():
sim = cosine_similarity(query_embedding, data["embedding"])
if sim > best_sim:
best_sim = sim
best_response = data["response"]
# 5. Decision
if best_sim > threshold:
print(f"Cache Hit! Similarity: {best_sim:.4f}")
return best_response
# 6. Cache miss – pay the token tax
response = llm.generate(user_query)
# Store both response and embedding for future matches
cache[user_query] = {
"response": response,
"embedding": query_embedding
}
return response
Note: 위 선형 검색은 예시일 뿐입니다. 캐시된 질의가 100개를 넘으면 Approximate Nearest Neighbor(ANN) 인덱싱을 지원하는 벡터 데이터베이스(예: pgvector, Pinecone, Weaviate, Qdrant)로 전환하세요.
위험 구역: False Positives
유사도 임계값을 너무 낮게(예: 0.7) 설정하면 False Positive Cache Hit이 발생할 수 있습니다.
- 질의: “Can I delete my account?”
- 캐시: “Can I delete my post?”
- 유사도: 0.85
사용자가 계정을 삭제하려는 상황에서 게시물 삭제 안내를 반환하면 심각한 UX 문제가 발생합니다.
프로덕션 팁: 민감한 동작에 대해서는 재순위(re‑ranker) 단계를 추가하세요. 잠재적인 캐시 히트를 찾은 뒤, 빠른 cross‑encoder 모델을 사용해 두 질의가 실제로 동일한 출력을 요구하는지 검증합니다.
요약
- 정확히 일치하는 캐싱: 구현은 쉽지만 규모가 커지면 비용이 많이 듭니다.
- 의미 기반 캐싱: 엔지니어링 노력이 더 필요하지만 API 비용을 약 40 % 절감할 수 있습니다.
수익성 있는 AI 애플리케이션을 만들려면 모델 선택뿐 아니라 탄탄한 시스템 엔지니어링이 핵심입니다.
연습할 곳
의미 기반 캐싱을 개념적으로 이해하는 것과 프로덕션 제약 하에서(임계값 튜닝, false‑positive 비율, 임베딩 비용 등) 디버깅하는 것은 큰 차이가 있습니다.
TENTROPY는 “Wallet Burner” 시나리오를 시뮬레이션하는 실전 과제를 제공합니다. 실시간 코드베이스를 다루며 불필요한 API 청구서를 확인하고, 벡터 로직을 구현해 비용 유출을 차단하게 됩니다.