Claude API를 쓰다 보면 시스템 메시지, tool 정의, 문서 본문처럼 매 요청마다 똑같이 보내는 긴 텍스트 블록이 생긴다.
Prompt Caching은 이 반복 구간을 KV(Key-Value) 캐시 형태로 저장해두고 다음 요청에서 그대로 재사용하는 메커니즘이다. 모델이 이미 처리한 토큰을 다시 계산하지 않으니 비용과 지연이 동시에 줄어든다. 실측치가 있다. 100K 토큰짜리 문서를 반복 처리할 때 첫 요청은 11.5초, 캐시 히트 이후엔 2.4초로 79% 단축됐다.
또한 캐시는 서버 디스크에 저장되지 않는다. 순수하게 메모리에만 존재하며, TTL이 만료되면 자동으로 삭제된다. 기본 TTL은 5분이고, 1시간 옵션도 있다. 캐시가 살아 있는 동안 히트가 발생하면 TTL은 그 시점부터 다시 초기화된다.
캐시 탐색 순서는 tools → system → messages다. 요청이 들어오면 Anthropic 서버는 이 순서대로 이전 캐시 항목과 비교한다. 중요한 건 prefix 일치 조건이다. 캐시가 히트되려면 두 가지를 동시에 만족해야 한다. 첫째, 이전 요청과 동일한 텍스트 prefix. 둘째, cache_control 마크가 정확히 같은 위치에 달려 있어야 한다. prefix 앞부분이라도 달라지면 미스로 처리된다.
브레이크포인트 위치를 잘못 잡으면 캐시가 조용히 실패한다. 예를 들어 시스템 메시지 중간이 아니라 끝 블록에 cache_control을 달아야 하는데, 앞 블록에 달면 그 이후 내용이 캐시 범위에서 빠진다.
요청당 명시적 브레이크포인트는 최대 4개까지 설정할 수 있다. 자동 캐싱 기능을 켜면 슬롯 하나가 자동으로 소모된다. 따라서 수동으로 브레이크포인트를 쓸 때는 자동 캐싱 활성화 여부를 먼저 확인하는 게 낫다.
비용 구조와 손익분기점 계산
비용 구조를 이해하면 캐싱이 언제 이득인지 직접 계산할 수 있다.
단가는 세 종류뿐이다. 표준 입력 단가를 1.0x로 놓으면, 캐시 쓰기는 TTL에 따라 1.25x(5분) 또는 2.0x(1시간), 캐시 읽기는 0.1x다. 읽기 단가가 표준의 10분의 1이니, 히트가 누적될수록 평균 단가가 급격히 내려간다.
2026년 4월 기준 가격표
| 항목 | Claude Sonnet 4.6 | Claude Opus 4.7 |
|---|---|---|
| 표준 입력 | $3.00/MTok | $15.00/MTok |
| 캐시 쓰기 — 5분 TTL | $3.75/MTok | $18.75/MTok |
| 캐시 쓰기 — 1시간 TTL | $6.00/MTok | $30.00/MTok |
| 캐시 읽기 | $0.30/MTok | $1.50/MTok |
| 출력 | $15.00/MTok | $75.00/MTok |
출력 토큰에는 캐싱이 적용되지 않는다. 절감은 오직 입력 쪽에서만 발생한다.
손익분기점 계산
5분 TTL의 손익분기점은 단순하다. 쓰기 1.25x + 첫 번째 캐시 읽기 0.1x = 1.35x. 동일 토큰을 표준으로 두 번 처리하면 2.0x이니 첫 히트 한 번만으로 이미 앞선다. 실시간 서버처럼 요청이 연속적으로 들어오는 구조라면 5분 TTL이 기본값으로 충분하다.
1시간 TTL은 다르다. 쓰기(2.0x) + 읽기 1회(0.1x) = 2.1x로, 표준 2회(2.0x)보다 아직 비싸다. 두 번째 읽기가 더해지면 2.2x — 표준 3회(3.0x)에 비해 비로소 저렴해진다. 히트가 2회 이상 보장될 때 1시간 TTL이 의미 있다. 배치 처리나 야간 파이프라인처럼 동일 프롬프트를 드문드문 재사용하는 경우가 여기에 해당한다.
나는 웬만하면 5분 TTL로 먼저 테스트하고, usage 필드에서 cache_read_input_tokens가 충분히 쌓이는지 확인한 뒤 1시간 TTL로 전환할지 판단한다.
실제 절감액 시뮬레이션
10만 토큰짜리 시스템 프롬프트를 하루 1,000회 호출하는 시나리오를 Sonnet 4.6 기준으로 계산해보면 차이가 명확하다.
캐싱 없음: 100K tokens × 1,000회 × 30일 = 3,000 MTok
→ 3,000 × $3.00 = $9,000/월
5분 TTL 적용: 호출 간격 약 1.44분이라 TTL이 계속 갱신된다. 하루 캐시 쓰기는 사실상 1~2회, 나머지 998회는 캐시 읽기로 처리된다.
→ 쓰기: 0.1 MTok × $3.75 × 30일 ≈ $11
→ 읽기: 99.8 MTok × $0.30 × 30일 ≈ $899
→ 합계: 약 $910/월
월 절감액은 $8,090, 절감률 약 90%다. Opus 4.7에 적용하면 단가가 5배이므로 동일 비율로 더 큰 금액이 절감된다. 시스템 프롬프트가 길수록, 호출 빈도가 높을수록 효과는 선형적이지 않아 더 빠르게 커진다.
모델별 최소 토큰 수와 TTL 선택 기준
캐싱이 활성화되려면 프롬프트 prefix가 모델별 최소 토큰 수를 넘어야 한다. 2026년 4월 기준으로 모델군마다 기준이 다르다.
| 모델 | 최소 캐시 토큰 |
|---|---|
| Claude Sonnet 4.5 · Opus 4 / 4.1 | 1,024 |
| Claude Sonnet 4.6 · Haiku 3.5 / 3 | 2,048 |
| Claude Opus 4.7 · Haiku 4.5 | 4,096 |
최소 토큰에 미달하면 캐시 쓰기 자체가 발생하지 않는다. 요금도 부과되지 않고, 조용히 표준 처리로 fallback된다. 청구서를 보기 전까지 캐싱이 동작하지 않는다는 사실을 모를 수 있다는 게 문제다. usage 필드에서 cache_creation_input_tokens가 계속 0이라면 첫 번째로 의심할 지점이 여기다.
TTL은 호출 패턴으로 고른다. 실시간 대화나 API 폴링처럼 요청 간격이 5분 이내인 워크로드라면 기본 5분 TTL로 충분하다. 캐시 히트가 발생할 때마다 TTL이 초기화되기 때문에 연속 호출에서는 사실상 무한히 유지된다. 반면 야간 배치 파이프라인이나 수십 분 간격의 문서 처리처럼 호출이 드문 경우라면 1시간 TTL을 선택해야 캐시 히트를 기대할 수 있다.
Opus 4.7를 쓴다면 주의가 필요하다. 이 모델은 신규 토크나이저를 도입했는데, 동일한 텍스트를 입력해도 이전 모델 대비 최대 35% 더 많은 토큰을 소비한다. 예를 들어 Sonnet 4.6에서 3,000토큰이었던 시스템 프롬프트가 Opus 4.7에서는 4,050토큰으로 늘어날 수 있다. 최소 임계값(4,096토큰)을 아슬하게 넘기거나 못 넘기는 경계에 있다면, 실제 API 호출 후 cache_creation_input_tokens 값으로 실측값을 반드시 확인해야 한다. 추정치를 믿으면 임계값 미달로 캐싱이 무효화되는 상황이 생긴다.
cache_control 설정과 브레이크포인트 배치
cache_control 설정의 핵심은 단순하다. 캐시하고 싶은 콘텐츠 블록의 마지막 블록 끝에 {"type": "ephemeral"} 을 붙인다. 이 마커가 캐시 브레이크포인트다. Claude API는 이 지점까지의 prefix 전체를 KV 캐시로 저장한다.
브레이크포인트는 요청당 최대 4개까지 설정할 수 있다. 자동 캐싱이 활성화된 환경(예: claude.ai 내부 호출)에서는 슬롯 1개를 자동 소모하므로, 명시적으로 쓸 수 있는 슬롯은 사실상 3개다.
예시 1 — 시스템 메시지 전체 캐싱
가장 흔한 패턴이다. 수천 토큰짜리 시스템 프롬프트를 매 요청마다 재전송하는 대신, 첫 호출에서 캐시에 올려두고 이후 요청은 읽기만 한다.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": "당신은 법률 문서 분석 전문가다. [... 10,000토큰 분량의 지침 ...]",
"cache_control": {"type": "ephemeral"}, # 브레이크포인트
}
],
messages=[{"role": "user", "content": "3조 2항을 요약해줘"}],
)
print(response.usage)
# 첫 호출: cache_creation_input_tokens=10000, cache_read_input_tokens=0
# 재호출: cache_creation_input_tokens=0, cache_read_input_tokens=10000
cache_creation_input_tokens와 cache_read_input_tokens 모두 0이면 캐싱이 작동하지 않는 상태다. 최소 토큰 임계값 미달이거나 브레이크포인트 위치가 잘못된 경우다.
예시 2 — tool 정의 + 시스템 메시지 동시에 브레이크포인트 설정
tool 정의가 길 때 tools 배열에도 브레이크포인트를 걸면 슬롯 2개를 써서 독립적인 캐시 구간 2개를 확보할 수 있다. 탐색 순서는 tools → system → messages 순이므로, tools 캐시와 system 캐시가 서로 간섭하지 않는다.
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=[
{
"name": "search_legal_db",
"description": "판례 데이터베이스에서 조건에 맞는 판례를 검색한다. [... 긴 설명 ...]",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"year_range": {"type": "array", "items": {"type": "integer"}},
},
"required": ["query"],
},
"cache_control": {"type": "ephemeral"}, # tools 브레이크포인트
}
],
system=[
{
"type": "text",
"text": "당신은 법률 분석 AI다. [... 시스템 프롬프트 ...]",
"cache_control": {"type": "ephemeral"}, # system 브레이크포인트
}
],
messages=[{"role": "user", "content": "2024년 저작권 판례를 찾아줘"}],
)
이 구조의 장점은 독립성이다. tool 정의가 바뀌더라도 system 캐시는 유지되고, 반대도 마찬가지다. 두 구간을 하나로 묶으면 어느 한쪽만 바뀌어도 전체 캐시가 무효화된다.
긴 문서 요약 시나리오
messages 배열 안에도 cache_control을 걸 수 있다. 50,000토큰짜리 계약서나 대용량 코드베이스처럼 반복 참조가 필요한 문서에 유용하다. user 메시지 블록을 문서 블록과 질문 블록으로 쪼개서 마커를 문서 블록 끝에만 붙인다.
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "<계약서 전문 — 약 50,000토큰>",
"cache_control": {"type": "ephemeral"}, # 문서만 캐시
},
{
"type": "text",
"text": "위약금 조항을 찾아서 한국어로 정리해줘",
# cache_control 없음 — 질문은 매번 달라지므로
},
],
}
]
문서와 질문을 분리하는 이유는 명확하다. 질문 텍스트가 바뀌어도 문서 캐시는 재사용된다. 반대로 둘을 하나의 블록에 합치면 질문이 바뀌는 순간 캐시 prefix가 달라져 미스가 발생한다.
Thinking 블록의 제약
thinking 블록에는 cache_control을 직접 붙일 수 없다. Thinking 블록은 독립적으로 캐시되는 게 아니라 주변 콘텐츠와 함께 하나의 캐시 단위로 묶인다. Extended Thinking을 쓰는 경우라면 thinking 블록 이후에 오는 일반 텍스트 블록에 브레이크포인트를 걸어야 전체 구간이 캐시 대상이 된다.
usage 필드로 캐시 적중을 검증하는 방법
캐시 설정을 마쳤다고 끝이 아니다. 실제로 캐시가 작동하는지 확인하지 않으면 비용이 줄었는지조차 알 수 없다. API 응답의 usage 필드 안에 두 값이 있다. cache_creation_input_tokens와 cache_read_input_tokens다.
세 가지 상태로 구분된다.
cache_creation_input_tokens > 0→ 해당 요청에서 캐시 쓰기가 발생했다. 5분 TTL이면 1.25x, 1시간 TTL이면 2.0x 비용이 청구된 것.cache_read_input_tokens > 0→ 캐시 히트 성공. 해당 토큰은 기본 입력가의 0.1x만 청구된다.- 두 값이 모두 0 → 캐싱이 전혀 동작하지 않는 상태. 최소 토큰 미달이거나
cache_control위치가 잘못된 경우다.
아래 코드는 각 요청마다 usage 값을 로깅하고 절감액을 실시간으로 계산한다. Sonnet 4.6 기준 입력 가격($3.00/MTok)을 상수로 박아뒀으므로 모델이 바뀌면 수정한다.
import anthropic
client = anthropic.Anthropic()
# Sonnet 4.6 기준 (2026년 4월 기준, $/MTok)
INPUT_PRICE_PER_MTOK = 3.00
CACHE_WRITE_5MIN_MULTIPLIER = 1.25
CACHE_READ_MULTIPLIER = 0.10
def call_with_cache_log(messages, system_prompt):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": system_prompt,
"cache_control": {"type": "ephemeral"},
}
],
messages=messages,
)
usage = response.usage
normal_tokens = usage.input_tokens
created = usage.cache_creation_input_tokens
read = usage.cache_read_input_tokens
# 표준 처리 가격 기준 가상 비용
standard_cost = (normal_tokens + created + read) / 1_000_000 * INPUT_PRICE_PER_MTOK
# 실제 청구 비용
actual_cost = (
normal_tokens / 1_000_000 * INPUT_PRICE_PER_MTOK
+ created / 1_000_000 * INPUT_PRICE_PER_MTOK * CACHE_WRITE_5MIN_MULTIPLIER
+ read / 1_000_000 * INPUT_PRICE_PER_MTOK * CACHE_READ_MULTIPLIER
)
saved = standard_cost - actual_cost
print(f"[캐시 상태]")
print(f" 일반 입력 토큰: {normal_tokens:,}")
print(f" 캐시 쓰기 토큰: {created:,} (x{CACHE_WRITE_5MIN_MULTIPLIER})")
print(f" 캐시 읽기 토큰: {read:,} (x{CACHE_READ_MULTIPLIER})")
print(f" 절감액 (이번 요청): ${saved:.6f}")
return response
cache_read_input_tokens가 계속 0으로 찍힌다면 두 가지를 먼저 확인한다. 캐시 브레이크포인트가 실제로 변하지 않는 prefix 끝에 위치해 있는지, 그리고 해당 모델의 최소 토큰 임계값을 넘겼는지다. Sonnet 4.6은 2,048토큰 미만이면 캐시 쓰기 자체가 발생하지 않는다.
OpenAI 호환 엔드포인트에서 캐싱이 작동하지 않는 이유
OpenAI SDK에서 base_url만 Anthropic 주소로 바꿔 Claude로 포팅하는 방식은 빠른 마이그레이션 패턴으로 자주 쓰인다. 문제는 이 방법으로 Prompt Caching을 설정하면 오류 없이 조용히 실패한다는 점이다. Anthropic 공식 문서에 명시된 사항이지만, 실제로 이 함정에 빠진 사례는 적지 않다.
원인은 단순하다. OpenAI 호환 엔드포인트인 /v1/chat/completions는 cache_control 필드를 파싱하지 않는다. 요청 본문에 "cache_control": {"type": "ephemeral"}을 추가해도 서버는 무시한다. 응답 usage에서 cache_creation_input_tokens와 cache_read_input_tokens는 둘 다 0이다. 캐시 쓰기도, 캐시 읽기도 없다 — 매 요청마다 표준 입력 요금 전액이 청구된다.
아래는 두 방식을 나란히 비교한 코드다.
# ❌ 캐싱 불가 — openai SDK + base_url 교체 방식
from openai import OpenAI
client = OpenAI(
api_key="sk-ant-...",
base_url="https://api.anthropic.com/v1",
)
response = client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[
{
"role": "system",
"content": [
{
"type": "text",
"text": "You are a legal document analyst...",
"cache_control": {"type": "ephemeral"}, # 무시됨
}
],
},
{"role": "user", "content": "Summarize this contract."},
],
)
# → cache_creation_input_tokens: 0, cache_read_input_tokens: 0
# → 표준 요금 100% 청구
# ✅ 캐싱 정상 동작 — anthropic SDK + /v1/messages
import anthropic
client = anthropic.Anthropic(api_key="sk-ant-...")
response = client.messages.create(
model="claude-sonnet-4-6-20251001",
max_tokens=1024,
system=[
{
"type": "text",
"text": "You are a legal document analyst...",
"cache_control": {"type": "ephemeral"}, # 정상 처리
}
],
messages=[{"role": "user", "content": "Summarize this contract."}],
)
# 첫 요청 → cache_creation_input_tokens: N > 0
# 이후 요청 → cache_read_input_tokens: N > 0
마이그레이션이 필요하다면 3단계로 처리한다.
- import 교체:
from openai import OpenAI→import anthropic - 엔드포인트 전환:
client.chat.completions.create(...)→client.messages.create(...)
-base_url파라미터 제거
-model문자열을 Anthropic 형식(claude-sonnet-4-6-20251001)으로 교체 cache_control블록 삽입: 시스템 메시지·tool 정의·문서 본문 등 반복되는 콘텐츠 끝 블록에"cache_control": {"type": "ephemeral"}추가
API 키 형식도 다르다. Anthropic 키는 sk-ant-로 시작한다. 마이그레이션 후 첫 요청 응답의 usage.cache_creation_input_tokens가 0이 아닌 값으로 찍히면 캐싱이 정상 동작하고 있다는 신호다. 이 값이 계속 0이라면 위 3단계 중 하나가 빠진 것이다.
Batch API와 결합해 비용을 95%까지 줄이는 전략
Prompt Caching만으로 입력 비용을 90% 줄일 수 있다면, Batch API까지 더하면 어디까지 가능할까.
Batch API는 표준 입력·출력 가격에서 50%를 일괄 할인한다. 대신 응답은 즉시 돌아오지 않는다. 요청을 묶어 제출하면 24시간 내에 처리 결과를 받는 구조다. 실시간 응답이 필요 없는 워크로드라면 충분한 조건이다.
두 기능은 중첩 적용된다. Sonnet 4.6 기준으로 계산하면:
| 단계 | 가격 (입력 기준) |
|---|---|
| 표준 입력 | $3.00 / MTok |
| Prompt Caching 캐시 읽기 적용 | $0.30 / MTok |
| Batch API 50% 추가 할인 | $0.15 / MTok |
표준 처리 대비 입력 비용이 95% 줄어든다. 출력 토큰에도 Batch API 50% 할인이 동일하게 붙는다.
이 조합이 유효한 워크로드는 세 가지다. 첫째, 동일한 긴 시스템 메시지를 반복하는 대량 문서 분류·요약 파이프라인. 수천 건을 같은 프롬프트로 돌리면 캐시 히트율이 거의 100%에 수렴한다. 둘째, 야간 배치 처리. 다음 날 결과만 있으면 되는 작업이라면 24시간 SLA는 충분하다. 셋째, 크롤링·태깅·레이블링 같은 비실시간 콘텐츠 생성 작업.
반대로 챗봇이나 실시간 API 응답처럼 수초 이내 결과가 필요한 경우는 Batch API를 쓸 수 없다. 그 경우 Prompt Caching 단독 적용으로 최대 90% 절감에 머문다. 두 기능 모두 적용 가능한 워크로드인지 먼저 확인하는 게 순서다.
멀티턴 대화에서의 20블록 룩백 제한, 워크스페이스별 캐시 격리 설정 같은 심화 전략은 [[INTERNAL_LINK:prompt-caching-advanced]]에서 이어서 다룬다.
관련해서 함께 읽으면 좋은 글
'AI 에이전트 > Claude 활용' 카테고리의 다른 글
| Claude Team SSO 설정 단계별 정리 (1) | 2026.04.23 |
|---|