Prompt Cache

Claude / OpenAI / Gemini의 캐싱 방식 비교

2026. 07. 02.
AILLMPromptCacheClaudeOpenAIGemini

앞 글에서 prompt cache의 원리("prefix 일치율")를 다뤘다. 이번 글에서는 그 원리를 세 provider가 각자 어떻게 구현했는지를 비교한다.

대충 결론부터.

말로 정리하면 이렇다.

Claude  : 개발자가 명시적으로 cache breakpoint를 박는다
OpenAI  : 자동. 호출 결과의 cached_tokens로 사후 확인
Gemini  : 자동(implicit) + 수동(explicit context caching) 두 모드

같은 코드를 세 provider에 그냥 옮기면 캐시 동작이 다르다. 그래서 "Agent 프레임워크가 알아서 해주겠지"라는 믿음은 매우 위험하다.


Claude (Anthropic)

어떻게 동작하나

명시적이다. 개발자가 cache_control을 박은 위치까지가 캐시 prefix가 된다.

import anthropic
 
client = anthropic.Anthropic()
 
response = client.messages.create(
    model="claude-sonnet-4-6",
    system=[
        {
            "type": "text",
            "text": LONG_SYSTEM_RULES,
            "cache_control": {"type": "ephemeral"},   # ← 캐시 경계
        }
    ],
    tools=[
        {
            "name": "search_docs",
            "description": "...",
            "input_schema": {...},
            "cache_control": {"type": "ephemeral"},   # ← tool도 캐시 가능
        }
    ],
    messages=[
        {"role": "user", "content": user_question},
    ],
)

캐싱 단위

ephemeral  : 약 5분 TTL (기본)
1h         : 1시간 TTL (가격 다름, opt-in)

어디까지 캐시할 수 있나

  • tool definitions (스키마 통째로)
  • system 메시지
  • messages 안의 텍스트 블록
  • 이미지/PDF도 캐시 가능
  • 한 요청에 최대 4개의 cache breakpoint

사용량 확인

응답의 usage에 다음이 잡힌다.

cache_creation_input_tokens : 이번 호출에서 캐시를 새로 만든 토큰 수
cache_read_input_tokens     : 이번 호출에서 캐시에서 읽은 토큰 수
input_tokens                : 그 외 일반 input

cache_read 비율이 높을수록 비용 효율이 좋다는 신호다.

가격 직관

cache write : 일반 input보다 약간 비쌈 (1.25x 정도)
cache read  : 일반 input보다 훨씬 쌈 (0.1x 수준)

처음 한 번은 약간 비싸지만, 두 번째 호출부터 본전 회수가 빠르다.

흔한 함정

- cache_control 위쪽 한 글자라도 다르면 cache miss
- system 안에 현재 시간 박으면 cache 안 잡힘
- breakpoint 다음에 또 dynamic 콘텐츠 → 두 번째 breakpoint 필요
- ephemeral 5분 TTL 안에 다음 호출이 안 들어오면 캐시 만료

OpenAI

어떻게 동작하나

자동이다. prefix가 같으면 자동으로 캐시 적용된다. 개발자가 cache_control 같은 마커를 박지 않아도 된다.

from openai import OpenAI
 
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": LONG_SYSTEM_RULES},
        {"role": "user",   "content": user_question},
    ],
)

이 호출이 일정 길이 이상이면 자동으로 prefix가 캐시 후보가 된다.

캐시 단위

- 보통 1024 토큰 정도 이상부터 캐시 대상
- prefix 일치율로 자동 판단

대화 history, tools, system 모두 prefix가 같다면 캐시될 수 있다.

사용량 확인

응답 usage에 다음 필드가 들어온다.

prompt_tokens
completion_tokens
prompt_tokens_details:
  cached_tokens       ← 이번 호출에서 캐시에서 읽은 양

cached_tokens / prompt_tokens 비율이 캐시 적중률 지표다.

가격 직관

cached input : 일반 input의 절반 정도
write 비용   : 별도로 부과되지 않음 (자동이라 투명함)

자동인 만큼 가격 모델도 단순하다.

흔한 함정

- prompt가 짧으면 캐시 대상에서 빠짐
- system 앞쪽에 매번 다른 metadata가 들어가면 캐시 깨짐
- "Agent framework가 messages를 매번 재조립"하면서 순서가 미세하게 바뀌면 miss
- 멀티 모델 라우팅하면 모델별로 캐시가 분리되어 hit률이 떨어짐

Gemini (Google)

Gemini는 두 가지 모드를 모두 제공한다.

모드 1: Implicit Caching (자동)

OpenAI처럼 동일 prefix를 자동으로 캐시한다. 별도 설정 없이 동작한다.

from google import genai
 
client = genai.Client()
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=[
        {"role": "user", "parts": [{"text": LONG_SYSTEM_RULES + user_question}]},
    ],
)

사용량에 cached_content_token_count가 잡힌다.

모드 2: Explicit Context Caching (수동 리소스)

이게 Gemini의 특이한 부분이다. 캐시를 별도 리소스로 만들어두고, 그 리소스를 참조해서 호출한다.

from google import genai
from google.genai import types
 
client = genai.Client()
 
# 1) 캐시 리소스 생성 (한 번)
cache = client.caches.create(
    model="gemini-2.5-pro",
    config=types.CreateCachedContentConfig(
        system_instruction="너는 사내 문서 도우미다. 규칙: ...",
        contents=[long_company_handbook],     # 큰 PDF/문서를 통째로
        ttl="3600s",
    ),
)
 
# 2) 캐시를 참조해서 호출 (여러 번)
response = client.models.generate_content(
    model="gemini-2.5-pro",
    contents=[user_question],
    config=types.GenerateContentConfig(cached_content=cache.name),
)

어디에 좋은가

- 큰 문서/책/매뉴얼을 같이 보면서 여러 번 질문할 때
- 같은 시스템 instruction + 같은 ground truth로 다수 사용자에게 답할 때
- 동영상, 긴 코드베이스 같은 대용량 입력

사용량 확인 / 가격 직관

cached_content_token_count  : 캐시에서 읽은 토큰 수
- explicit caching은 저장 비용도 같이 들어감 (시간당 GB 단가)
- 호출당 비용은 일반 input의 1/4 정도 (모델/사이즈에 따라)

저장 비용이 있기 때문에 TTL이 중요하다. 안 쓸 캐시는 빨리 삭제한다.

흔한 함정

- explicit cache는 모델별/리전별로 따로 만들어야 함
- 캐시 리소스 관리(생성/삭제)가 백엔드 책임
- TTL 만료 후엔 재생성 비용 발생
- implicit는 prefix 깨지면 OpenAI처럼 그냥 miss

한 장 비교표

항목ClaudeOpenAIGemini (implicit)Gemini (explicit)
제어 방식명시적 (cache_control)자동자동명시적 (리소스 생성)
캐시 단위텍스트 블록 (tool/system/messages)prefix 일치prefix 일치별도 캐시 리소스
TTL5분 (ephemeral) / 1시간 (opt-in)짧음, 사용 빈도에 따라 자동짧음사용자가 지정 (초 단위)
사용량 확인cache_read_input_tokenscached_tokenscached_content_token_count같음
가격 직관write 약간 비쌈, read 매우 쌈cached input은 약 절반동일input의 약 1/4 + 저장 비용
대용량 입력가능 (이미지/PDF 캐시)자동자동특히 강함 (책, 동영상)
함정breakpoint 위쪽 한 글자 변화매번 재조립되는 messages 순서짧은 prompt는 caching 안 됨리소스 관리, TTL

실무 권장

세 가지 provider를 동시에 다 쓰는 회사라면 다음 원칙이 안전하다.

1. layout은 provider-agnostic하게 설계

system, tools, few-shot은 무조건 위로. 가변값(시간, request id, retrieved docs)은 무조건 아래로.

이 규칙은 어느 provider에 가져가도 깨지지 않는다.

2. Claude는 cache_control을 의식적으로 박는다

자동 캐시가 없으니 안 박으면 일이 안 일어난다.

# 안전한 위치 두 군데
[ tools ] → cache_control
[ system + few-shot ] → cache_control

3. Gemini는 "큰 문서가 있는 RAG"라면 explicit caching 검토

수십~수백 페이지짜리 매뉴얼을 매번 prompt에 끼워 보내는 RAG라면 explicit caching이 압도적이다. TTL을 1일 정도로 잡고, 매뉴얼이 업데이트되면 캐시를 새로 만든다.

4. cache 효율은 항상 계측

Langfuse trace에서 매번 cached_tokens(또는 cache_read)를 본다.

한 trace에서 cached_input이 60% 이상 → 잘 동작
cached_input이 0%로 떨어지면 alert

알람으로 "캐시가 갑자기 0%로 떨어졌다"가 잡혀야 비용 폭발 사고를 막을 수 있다.


결론

Provider마다 prompt cache 정책이 다르기 때문에, Agent framework가 알아서 최적화해준다고 믿으면 안 된다. system prompt, tool definition, dynamic context, retrieved docs, conversation history를 어디에 배치할지 직접 설계해야 한다.

LangChain이든 LangGraph든 prompt 위치를 자동으로 최적화해주지 않는다. 이건 결국 백엔드 설계의 책임이고, 그 결과는 월별 청구서로 즉시 드러난다.

캐시를 다음 세 줄로 요약하면.

1. prefix를 길고 고정되게 만든다
2. 동적값을 prefix에 절대 섞지 않는다
3. provider별 캐시 사용량을 항상 계측한다

이 세 줄을 지키면 같은 LLM 앱이 같은 사용자에게 같은 답을 내면서, 비용은 절반이 된다.