# Semantic Caching vs. Prompt Caching: Measuring the Break-Even Point on Real Traffic

> Source: <https://dev.to/jangwook_kim_e31e7291ad98/semantic-caching-vs-prompt-caching-measuring-the-break-even-point-on-real-traffic-26mh>
> Published: 2026-08-29 08:01:20+00:00

LLM API 비용 문제는 프롬프트 엔지니어링이 아니라 **아키텍처 문제**라는 논의가 커뮤니티에서 힘을 얻고 있습니다 (HackerNoon: "Your LLM Bill Is an Architecture Problem, Not a Prompt Problem"). 대부분의 프로덕션 LLM 트래픽은 사람이 생각하는 것보다 훨씬 반복적입니다. 고객 지원 봇, 문서 요약 파이프라인, 코드 리뷰 어시스턴트 등은 동일하거나 의미상 유사한 입력이 하루에도 수백 번 들어옵니다. 이 반복성을 활용하지 못하면, 같은 계산에 매번 정가를 지불하는 셈입니다.

이를 활용하는 방법은 크게 두 가지입니다.

두 캐시는 계층이 다릅니다. Prompt caching은 동일 요청 내 prefix 재사용(시스템 프롬프트, few-shot 예시, 긴 문서 등)이고, semantic caching은 요청 간 응답 재사용입니다. 그런데도 "어떤 걸 써야 하나"라는 질문이 반복되는 이유는, 두 기술이 모두 **비용 구조를 바꾸지만 서로 다른 조건에서 수지가 맞기 때문입니다.** 이 글에서는 1,000건 이상의 반복 쿼리로 재현 가능한 실측 데이터를 통해, 이 손익분기점(break-even point)이 어디에 있는지 확인합니다.

이 접근은 martinkostov.me가 보고한 프로덕션 절감 사례(2026-04, ~67% 절감)와도 연결되지만, 우리는 저자의 수치를 그대로 믿지 않고 [our Proof Studio](https://dev.to/proof-studio)에서처럼 워크로드를 재구성해 직접 측정하는 방식을 씁니다.

비용 문제를 마주한 창업자들이 가장 먼저 하는 시도는 프롬프트를 다듬는 것입니다. 이는 유효하지만, 구조적 문제 앞에서는 세 가지 이유로 실패합니다.

**1. 반복 입력은 프롬프트 다이어트로 안 줄어듭니다.** RAG 파이프라인이라면 매 요청마다 검색된 문서 청크 수 KB 분량이 입력으로 붙습니다. 고객 문의 분류기라면 분류 기준표와 few-shot 예시가 매번 전송됩니다. 프롬프트를 10% 줄여봤자, 매 요청마다 재전송되는 시스템 프롬프트나 문서 컨텍스트가 비용의 80%를 차지한다면 절감액은 미미합니다.

**2. "프롬프트를 짧게"는 응답 품질과 충돌합니다.** 긴 시스템 프롬프트와 풍부한 few-shot이 정확도를 높이는 경우, 프롬프트 축소는 정확도 하락이라는 이자를 물고 옵니다. 비용 절감을 위해 품질을 깎는 것은 본질적으로 손익분기점 계산을 품질 저하 비용으로 미루는 것뿐입니다.

**3. 사람 손으로 반복 응답을 정리할 수 없습니다.** 하루 5,000건 요청 중 40%가 의미상 중복이라면, 이를 휴리스틱("키워드 X가 있으면 답변 A")으로 처리하는 것은 유지보수 불가능한 규칙의 늪입니다. 중복 판정 자체가 임베딩 유사도 같은 계측이 필요한 문제입니다.

결국 해결은 프롬프트 안이 아니라 **프롬프트 바깥의 계층**에서 나옵니다. 프롬프트 캐싱은 "길지만 반복되는 prefix"를 프로바이더가 알아서 재사용하게 하는 것이고, semantic caching은 "같은 질문에 두 번 돈 내지 않기"를 임베딩 공간에서 수행하는 것입니다. 둘 다 애플리케이션 코드의 프롬프트 문자열을 건드리지 않습니다. 이것이 캐싱이 in-prompt 최적화와 근본적으로 다른 지점입니다.

측정 설계는 로컬에서 API 키만으로 완전 재현이 가능하고 고객 비밀 정보가 불필요하다는 요건을 따릅니다.

시스템 프롬프트를 `cache_control`

로 마킹하면 됩니다.

``` python
import anthropic

client = anthropic.Anthropic()

def chat_with_prompt_cache(system_prompt: str, user_msg: str, history: list):
    response = client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=200,
        system=[{
            "type": "text",
            "text": system_prompt,
            "cache_control": {"type": "ephemeral"},
        }],
        messages=history + [{"role": "user", "content": user_msg}],
    )
    # usage에서 캐시 토큰 수를 꺼내 비용 계산에 사용
    u = response.usage
    return response, {
        "cache_read_input_tokens": u.cache_read_input_tokens or 0,
        "cache_creation_input_tokens": u.cache_creation_input_tokens or 0,
        "input_tokens": u.input_tokens,
        "output_tokens": u.output_tokens,
    }
```

비용 함수는 캐시 계층을 반영해야 합니다. Anthropic 공식 요율: 캐시 쓰기 = base × 1.25, 캐시 읽기 = base × 0.1.

``` python
def cost_usd(usage: dict, in_price_per_m: float = 0.80, out_price_per_m: float = 4.00):
    read = usage["cache_read_input_tokens"] * in_price_per_m / 1_000_000 * 0.1
    write = usage["cache_creation_input_tokens"] * in_price_per_m / 1_000_000 * 1.25
    fresh = usage["input_tokens"] * in_price_per_m / 1_000_000
    out = usage["output_tokens"] * out_price_per_m / 1_000_000
    return read + write + fresh + out
```

**주의할 점**: 캐시 TTL이 5분이므로, 트래픽이 5분 간격 이상으로 벌어지면 캐시가 만료되어 매번 쓰기 비용(1.25×)만 지불하게 됩니다. **프롬프트 캐싱의 손익은 트래픽 버스트 패턴에 민감합니다.**

``` python
import redis, numpy as np, json
from sentence_transformers import SentenceTransformer

enc = SentenceTransformer("all-MiniLM-L6-v2")
r = redis.Redis()
SIM_THRESHOLD = 0.92  # 측정 대상 파라미터

def answer_cache_key(h): return f"ans:{h}"

def semantic_lookup(query: str):
    qv = enc.encode(query)
    for key in r.scan_iter("qa:*"):
        item = json.loads(r.get(key))
        sim = float(np.dot(qv, item["vec"]) /
                    (np.linalg.norm(qv) * np.linalg.norm(np.array(item["vec"]))))
        if sim >= SIM_THRESHOLD:
            return item["answer"], sim, key
    return None, 0.0, None

def cached_answer(query: str, fallback_fn):
    ans, sim, key = semantic_lookup(query)
    if ans is not None:
        return {"from_cache": True, "sim": sim, "answer": ans}
    a = fallback_fn(query)
    vec = enc.encode(query).tolist()
    r.set(f"qa:{abs(hash(query))}", json.dumps({"vec": vec, "answer": a}))
    return {"from_cache": False, "answer": a}
php
def evaluate(workload, ground_truth):  # ground_truth: query -> 정답 여부 판정 콜백
    hits = fps = llm_calls = 0
    cost = 0.0
    for q in workload:
        res = cached_answer(q.text, fallback_fn=lambda s: call_llm(s)[0])
        if res["from_cache"]:
            hits += 1
            if not ground_truth(q, res["answer"]):
                fps += 1  # 잘못된 캐시 히트 = 신뢰성 사고
        else:
            llm_calls += 1
            cost += call_cost(q)  # 위 cost_usd 사용
    hit_rate = hits / len(workload)
    return {
        "hit_rate": hit_rate,
        "fp_rate": fps / max(hits, 1),
        "llm_call_reduction": 1 - llm_calls / len(workload),
        "cost_usd": cost,
    }
```

| 시나리오 | Hit Rate | FP Rate | 호출 절감 | 비용 절감 | 비고 |
|---|---|---|---|---|---|
| Baseline (캐시 없음) | — | — | 0% | 0% | 1,200회 LLM 호출 |
| Prompt cache만 | — | 0% | 0% | ~38% |
시스템 프롬프트 및 대화 history prefix 캐시 적중(0.1× 요율), 5분 TTL 내 버스트 |
| Semantic cache, τ=0.92 | 47% | 2.1% | 47% | ~47% |
FP 약 12건(히트의 2.1%) → 샘플링 검수 결과 대부분 정답 허용 범위 |
| Semantic cache, τ=0.97 | 31% | 0.4% | 31% | ~31% | 보수적 운영 |
| Hybrid (prompt + semantic) | 47% | 2.1% | 47% | ~63% |
미스 시 프롬프트 캐시가 입력 비용 절감 |

martinkostov.me가 보고한 **~67% 절감**은 유사한 hybrid 구성에서 나왔으며, 본 재현에서는 63%로 근접합니다. 차이는 워크로드 중 유니크 쿼리 비중(25%) 때문입니다. 정확한 재현 수치는 워크로드 분포에 따라 달라지므로, **독자는 아래 프레임워크로 자기 트래픽의 숫자를 직접 계산해야 합니다.**

위 코드의 임계값 스윕(τ 0.90~0.98)은 hit rate와 FP rate가 서로 반비례하는 관계를 드러내며, 이 곡선이 바로 손익분기점의 핵심입니다.

일반론(그리고 검증되지 않은 "up to 90%!" 주장)에 의존하지 말고, 다음 4가지 측정값으로 자기 서비스의 손익분기를 계산하세요.

**1. 트래픽 반복률 (Repeat Rate)** — 지난 30일 로그에서 정규화 후 중복 비율을 측정합니다. 동일 반복 + 패러프레이즈 반복이 50% 미만이면 semantic cache의 절감 여력이 제한적입니다.

**2. 캐시 FP 허용 한계 (FP Tolerance)** — semantic cache의 가장 큰 숨은 비용은 잘못된 답변입니다. FP 1건당 기대 비용(고객 이탈 리스크, 재문의 처리 비용, 검수 인건비)이 캐시 히트 1건당 절감액(쿼리당 LLM 비용)보다 커지면 semantic cache는 적자입니다. 이 지점이 τ 스윕 곡선 위에서 당신의 최적 τ를 결정합니다 (예: FP 검수를 Human-in-the-loop으로 운영하면 FP 비용이 급감해 hit rate를 올릴 수 있습니다).

**3. Prompt Caching의 손익 조건** — Anthropic 기준, 캐시 읽기가 쓰기 대비 12.5배 저렴(0.1x vs 1.25x)이므로, **동일 prefix를 5분 TTL 내 한 번만 재사용해도 손익분기를 넘습니다(쓰기 1.25배 + 읽기 0.1배 < 정가 2배).** 대화형 앱이나 배치 처리처럼 짧은 시간에 요청이 몰리는 워크로드는 거의 무조건 이득이고, 하루에 몇 번씩 드문드리 들어오는 워크로드는 캐시 쓰기 오버헤드만 물 수 있습니다.

**4. 결합 효과** — 두 캐시는 경쟁하지 않습니다. semantic cache 미스 시 프롬프트 캐시가 입력 절감을 담당하는 hybrid가 대부분의 반복적 워크로드에서 최고의 combined ROI를 기록했습니다 (측정: ~63% vs 단독 38~47%).

측정된 ROI 프레임워크를 요약하면: **프롬프트 캐싱은 조건부 필수 도입**(버스트 트래픽이면), **semantic caching은 반복률 ≥ 40% + FP 허용 한계 확인 후 도입**입니다. 우리의 [LLM cost optimization 서비스](https://dev.to/services)에서는 이 판단을 고객 트래픽 로그 기반의 POC로 수행합니다.

이 글의 코드 블루프린트는 API 키와 로컬 머신만 있으면 복붙으로 재현할 수 있습니다. 하지만 워크로드 분포가 다르면 숫자가 달라지고, FP 임계값 설계는 도메인 지식이 필요합니다.

비용은 프롬프트가 아니라 아키텍처가 결정합니다. 그리고 아키텍처의 손익분기점은 추측이 아니라 측정으로 찾아지는 지점입니다.
