왜 캐시가 안 맞는가
Anthropic이나 유사한 LLM API의 프롬프트 캐시는 접두사(prefix) 기반이다. 프롬프트의 맨 앞부터 캐시 브레이크포인트까지의 토큰 열이 바이트 단위로 완전히 동일할 때만 적중한다. 문제는 대부분의 애플리케이션이 시스템 프롬프트 앞이나 중간에 매번 바뀌는 값을 끼워 넣는다는 점이다. 예를 들어 시스템 프롬프트 상단에 현재 시각: 2026-09-15 14:03:22 같은 줄이 하나 있으면, 그 아래 수천 토큰짜리 도구 정의와 예시가 아무리 고정이어도 접두사가 매 요청마다 깨져 캐시가 전부 무효화된다.
즉 캐시 미스의 진짜 원인은 콘텐츠 양이 아니라 변하는 것과 고정된 것을 섞어 놓은 순서다.
콜드와 핫의 구분
프롬프트 구성 요소를 두 부류로 나눈다.
- 콜드(cold) 세그먼트: 요청 간 거의 변하지 않는다. 시스템 지침, 도구/함수 정의, few-shot 예시, RAG용 고정 문서, 정책 텍스트.
- 핫(hot) 세그먼트: 매 요청 바뀐다. 사용자 질문, 현재 시각, 세션 상태, 방금 검색된 문서, 대화 마지막 턴.
규칙은 단순하다. 콜드를 전부 앞으로, 핫을 전부 뒤로 배치하고, 콜드가 끝나는 지점에 캐시 브레이크포인트를 찍는다. 이렇게 하면 콜드 세그먼트는 한 번 캐시 기록 후 TTL(기본 5분, 옵션 1시간) 동안 재사용된다.
비용 구조로 보는 이득
| 구분 | 기본 입력 대비 배수 | 의미 |
|---|---|---|
| 캐시 미스(일반 입력) | 1.0x | 매번 전체 요금 |
| 캐시 기록(5분 TTL) | 약 1.25x | 최초 1회만 더 냄 |
| 캐시 기록(1시간 TTL) | 약 2.0x | 장수명 콜드용 |
| 캐시 적중(읽기) | 약 0.1x | 이후 모든 재사용 |
콜드 세그먼트가 크고 반복 호출이 많을수록 이득이 커진다. 10K 토큰 콜드를 100번 재사용하면, 기록 1회(1.25x)와 읽기 99회(0.1x)로 끝나 단순 반복 대비 입력 비용이 대략 8~9할 감소한다.
구현: 브레이크포인트 배치
아래는 콜드/핫을 분리하고 콜드 끝에만 cache_control을 붙인 예시다. 도구 정의와 시스템 지침은 위, 사용자 입력은 messages 뒤쪽에 둔다.
import anthropic
client = anthropic.Anthropic()
# 콜드: 고정 시스템 지침 + 대용량 정책/예시
COLD_SYSTEM = load_static_policy() # 수천 토큰, 배포 전까지 불변
resp = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
system=[
{
"type": "text",
"text": COLD_SYSTEM,
# 콜드가 끝나는 지점에 브레이크포인트
"cache_control": {"type": "ephemeral"},
}
],
messages=[
# 핫: 매번 바뀌는 사용자 질의는 캐시 마킹 없이 뒤에
{"role": "user", "content": user_query},
],
)
print(resp.usage) # cache_creation_input_tokens / cache_read_input_tokens 확인
usage의 cache_read_input_tokens가 콜드 크기만큼 잡히면 적중한 것이다. 0이면 접두사 어딘가가 깨진 것이므로 콜드 안에 핫이 섞였는지 점검한다.
콜드 안정화 체크리스트
콜드를 진짜 콜드로 유지하려면 다음을 콜드 구간에서 제거한다.
# 콜드에 절대 넣지 말 것 (접두사를 깨는 흔한 원인)
- 현재 시각 / 타임스탬프
- 요청 ID, trace ID, 세션 토큰
- 사용자 이름·이메일 등 개인화 값
- JSON 직렬화 시 키 순서가 흔들리는 dict
-> json.dumps(obj, sort_keys=True) 로 고정
- 매번 재정렬되는 RAG 문서 목록
-> 문서 순서를 안정 정렬로 고정하거나 hot 구간으로 이동
특히 직렬화 순서는 눈에 안 띄는 미스의 단골이다. 도구 정의를 코드에서 dict로 만들어 넘기면 파이썬 버전이나 로직 변경에 따라 키 순서가 달라질 수 있으니 정렬을 강제한다.
다단계 캐시와 TTL 선택
브레이크포인트는 여러 개(보통 최대 4개) 둘 수 있다. 변화 주기가 다른 층을 나눠서 계단식으로 캐싱하면 재사용률이 더 오른다. 예: (1) 전역 시스템 지침 → (2) 조직별 정책 → (3) 세션별 대화 요약 순으로, 각 층 끝에 브레이크포인트를 둔다. 앞쪽일수록 오래 사는 콜드, 뒤로 갈수록 준-핫이다.
TTL은 호출 간격으로 정한다. 요청이 5분 안에 촘촘히 이어지면 기본 5분으로 충분하고, 간헐적이지만 콜드가 매우 큰 배치성 워크로드는 1시간 TTL(기록 2x)이 총비용에서 유리할 수 있다. 실측으로 적중률을 보고 결정한다.
주의점
캐시는 최소 토큰 길이 조건이 있다. 콜드가 너무 작으면(예: 1024 토큰 미만) 브레이크포인트를 찍어도 캐시되지 않으므로, 잘게 쪼개지 말고 콜드끼리 묶어 임계값을 넘겨야 한다. 또한 모델 버전이나 도구 정의를 바꾸면 콜드가 통째로 무효화되니, 배포 시 캐시 워밍업 호출을 한 번 넣어 첫 사용자 지연을 줄인다. 마지막으로 캐시 적중률은 추측하지 말고 usage 필드를 로깅해 대시보드로 관찰하는 것이 실무의 기본이다.