~/ai-dev.md
AI_DEV

Prompt caching은 왜 ‘자동 할인’이 아니라 프롬프트 구조 계약일까

10분 읽기·2026.06.18·출처 4·00
오늘의 질문

LLM 앱에서 반복되는 긴 컨텍스트를 싸고 빠르게 쓰려면, 프롬프트를 어떤 순서로 고정해야 할까?

ai-dev.md
SURVIVE.exe

Prompt caching은 왜 ‘자동 할인’이 아니라 프롬프트 구조 계약일까

1. 왜 지금 봐야 하나

LLM 비용 최적화를 이야기하면 보통 “더 싼 모델을 쓰자”, “토큰을 줄이자”, “배치를 쓰자”로 끝나기 쉽다. 그런데 실제 제품에서는 다른 병목이 자주 나온다. 매 요청마다 같은 시스템 프롬프트, 같은 도구 정의, 같은 출력 스키마, 같은 프로젝트 문맥, 같은 긴 문서를 반복해서 모델에 보낸다는 점이다.

OpenAI, Anthropic, Google Gemini 모두 prompt/context caching을 문서화하고 있다. 표면적으로는 “반복 프롬프트를 캐시해서 비용과 지연을 줄인다”는 기능처럼 보인다. 하지만 개발자 관점에서는 이것이 단순 할인 옵션이 아니다. 캐시가 먹히는 조건은 대부분 프롬프트 앞부분이 얼마나 안정적인가에 달려 있다.

즉 캐싱은 API 공급자가 알아서 해주는 마법이 아니라, 애플리케이션이 지켜야 하는 프롬프트 구조 계약이다. 이 계약을 모르면 긴 컨텍스트 앱은 테스트 때는 괜찮다가, 실제 트래픽에서 캐시 미스·지연 증가·예상 밖 비용으로 흔들린다.

2. 핵심 개념

Prompt caching의 핵심은 “이전 요청에서 처리한 prefix를 다음 요청에서 다시 쓴다”이다. 여기서 prefix는 프롬프트의 앞부분이다. 일반적인 요청을 단순화하면 다음 순서로 쌓인다.

zsh — 생존확인.sh
고정 시스템 지시문
→ 도구 정의
→ 출력 스키마
→ 예시
→ 공통 문서/프로젝트 문맥
→ 이전 대화 또는 작업 기록
→ 이번 사용자 질문
→ 이번 요청에서만 필요한 동적 데이터

캐시는 대개 이 앞부분이 이전 요청과 같을 때 히트한다. OpenAI 문서는 반복되는 static content를 앞에 두고, dynamic content를 뒤에 두라고 안내한다. Anthropic 문서는 tools → system → messages 순서의 prompt hierarchy와 cache breakpoint를 강조한다. Claude Code 문서는 더 직관적으로 말한다. “prefix의 어느 지점이 바뀌면 그 뒤는 다시 계산된다. per-file 또는 per-segment caching이 아니다.”

그래서 설계 원리는 간단하다.

자주 바뀌지 않는 것은 앞에, 매번 바뀌는 것은 뒤에 둔다.

하지만 실무에서는 이 간단한 원리가 자주 깨진다. 예를 들어 요청마다 현재 시간, 사용자 ID, A/B 실험 플래그, 랜덤 trace id를 시스템 프롬프트 앞부분에 끼워 넣으면 그 뒤의 긴 도구 정의와 문서 문맥까지 캐시 재사용이 어려워질 수 있다. “프롬프트 내용은 거의 같은데 왜 캐시가 안 되지?”라는 문제는 보통 여기서 시작한다.

3. 최신 이슈와 연결

2026년 현재 각 공급자의 캐싱은 비슷한 목표를 갖지만 제어 방식은 다르다.

OpenAI prompt caching은 지원 모델에서 자동으로 동작하며, 일정 길이 이상의 요청에서 반복 prefix가 맞으면 usage.prompt_tokens_details.cached_tokens로 캐시된 토큰 수를 확인할 수 있다. 문서에는 prompt_cache_key와 retention 정책도 설명되어 있다. 중요한 점은 hit가 “비슷한 의미”가 아니라 prefix 일치에 가깝다는 것이다.

Anthropic은 더 명시적인 제어축을 제공한다. top-level cache_control로 자동 캐싱을 켜거나, content block에 직접 cache breakpoint를 둘 수 있다. 기본 TTL은 5분이고, 1시간 TTL은 별도 옵션과 비용 구조가 있다. 또 cache write와 cache read 가격이 다르기 때문에, 한 번 쓰고 끝나는 거대한 프롬프트는 오히려 캐시 write 비용만 늘릴 수 있다.

Gemini API는 implicit caching과 explicit caching을 나눈다. Gemini 2.5 이상에서는 implicit caching이 기본으로 켜져 있지만, 문서는 “비용 절감 보장”이 있는 것은 explicit caching이라고 구분한다. explicit caching은 cached content 객체를 만들고 TTL을 관리하는 방식이라 구현 부담이 늘지만, 긴 문서·영상·반복 분석 작업에는 더 예측 가능한 구조가 된다.

Claude Code 문서는 운영자에게 유용한 힌트를 준다. 모델 변경, effort 변경, MCP 서버 연결/해제, 도구 거부, compact, 업그레이드 같은 행동이 캐시를 깨거나 재구축하게 만들 수 있다고 설명한다. 이것은 API 앱에도 그대로 적용된다. 모델·도구 목록·시스템 지시문·출력 스키마가 캐시 키의 일부처럼 행동한다고 생각해야 한다.

4. 개발자 관점 해석

Prompt caching을 제대로 쓰려면 “프롬프트 문자열을 예쁘게 만든다”보다 “변경 빈도별로 계층을 나눈다”가 먼저다.

첫째, 고정 계층을 만든다. 제품 정책, 역할 지시, 도구 목록, JSON Schema, few-shot 예시는 가능하면 요청 앞쪽에 고정한다. 버전이 바뀔 때만 바뀌어야 한다.

둘째, 준고정 계층을 분리한다. 프로젝트 문서, 고객별 knowledge base, 긴 파일 묶음은 매 요청마다 새로 조립하지 말고 안정적인 순서와 포맷으로 묶는다. Gemini explicit caching처럼 별도 cache object로 다룰 수 있으면 더 명확하다.

셋째, 동적 계층은 뒤로 보낸다. 사용자 질문, 현재 화면 상태, 검색 결과, 최근 tool output, trace id, 요청별 옵션은 뒤쪽으로 밀어야 한다.

넷째, “캐시 히트율”을 별도 지표로 본다. 평균 latency나 평균 input token 비용만 보면 원인을 놓친다. OpenAI의 cached_tokens, Gemini의 usage_metadata, Anthropic의 usage 필드를 로그에 남겨야 한다. 이상적인 로그는 최소한 아래처럼 보여야 한다.

zsh — 생존확인.sh
model
prompt_version
tool_schema_version
static_prefix_tokens
input_tokens
cached_input_tokens
cache_write_tokens
cache_read_tokens
TTFT
end_to_end_latency

다섯째, 실패 모드를 알아야 한다.

  • prefix 앞부분에 동적 값이 섞여 매번 cache miss가 난다.
  • 도구 정의를 요청마다 다른 순서로 직렬화한다.
  • JSON Schema에 timestamp나 요청별 enum을 넣는다.
  • RAG 검색 결과를 너무 앞에 넣어 공통 prefix를 밀어낸다.
  • 모델이나 reasoning effort를 자주 바꿔 캐시를 새로 만든다.
  • TTL보다 긴 간격으로 요청이 와서 매번 cold start처럼 동작한다.
  • 캐시 write 비용을 고려하지 않고 1회성 대형 프롬프트를 캐시한다.

이런 실패는 모델 성능 문제가 아니다. 대부분 애플리케이션의 프롬프트 조립 방식 문제다.

5. 내 프로젝트에 적용할 체크포인트

LLM 기능을 운영 중이라면 아래 질문으로 점검해보자.

  1. 프롬프트 앞 1,000~4,000 토큰이 요청마다 동일한가?
    OpenAI와 Gemini 문서 모두 일정 토큰 이상에서 캐싱 조건이 의미를 갖는다. 짧은 프롬프트는 캐싱보다 단순화가 먼저일 수 있다.

  2. 도구 정의와 스키마의 순서가 결정적인가?
    객체 순서가 매번 달라지는 직렬화, feature flag에 따라 흔들리는 tool list는 캐시를 깨기 쉽다.

  3. RAG 결과를 어디에 붙이는가?
    검색 결과는 요청마다 바뀌므로 보통 뒤쪽에 둬야 한다. 반복되는 코퍼스 자체는 explicit cache나 별도 문맥 계층으로 분리하는 편이 낫다.

  4. 프롬프트 버전을 관리하는가?
    시스템 프롬프트를 조금만 고쳐도 캐시는 새로 만들어진다. 배포 시점에 비용·latency가 일시적으로 튈 수 있음을 관측해야 한다.

  5. 캐시 비용 모델을 이해했는가?
    Anthropic처럼 cache write와 read 가격이 다른 경우, “몇 번 이상 재사용해야 이득인가”를 계산해야 한다. Gemini explicit caching도 TTL과 저장 비용을 함께 봐야 한다.

  6. 개인정보와 보존 정책을 확인했는가?
    캐시는 성능 기능이면서 데이터 보존 정책과 연결된다. 각 공급자의 ZDR, retention, workspace/project isolation 설명을 확인해야 한다.

6. 오늘 10분 액션

오늘은 코드를 크게 바꾸지 말고, 현재 LLM 호출 하나를 골라 “캐시 친화도”만 표시해보자.

  1. 실제로 보내는 최종 prompt/messages/tools를 로그 샘플 3개로 저장한다. 민감정보는 마스킹한다.
  2. 세 요청의 앞부분 2,000토큰 안에서 매번 달라지는 줄을 표시한다.
  3. 달라지는 값이 system/developer/tool/schema 영역에 있으면 뒤쪽 user/request 영역으로 옮길 수 있는지 적는다.
  4. usage 응답에서 cached token 관련 필드를 저장하도록 로그 스키마를 추가한다.
  5. 다음 배포에서 prompt_version, tool_schema_version, cached_tokens, TTFT를 한 화면에 같이 본다.

이 10분만 해도 “모델이 느리다”와 “캐시가 매번 깨진다”를 구분할 수 있다.

7. 더 볼 자료

중복 회피 메모

최근 ai_dev 글은 긴 컨텍스트 메모리, 에이전트 권한 계약, prefill/decode 지연, RAG 검색 품질, Tool Search, KV 캐시 양자화, 도구 선택 파인튜닝, LLM-as-judge, speculative decoding, structured outputs를 다뤘다. 이번 글은 긴 컨텍스트 자체나 서빙 단계의 KV cache가 아니라, API 요청의 반복 prefix를 비용·지연 최적화 대상으로 만드는 prompt/context caching과 프롬프트 조립 순서의 운영 계약에 집중한다.

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)