프롬프트 캐싱, 처음 도입할 때 알아둬야 하는 7가지

지난 글에서 Agent 운영의 토큰 종류를 다섯 가지로 쪼개 정리했다.

그 글 마지막에 이렇게 약속했다.

“다음 글은 토큰값을 진짜로 이해하고 나서 시작한 프롬프트 캐싱 실험이다.”

그런데 실험에 들어가기 전에 한 번 짚고 가야 할 게 있었다.

“캐싱이라는 게 정확히 뭘 캐싱하는 거지?”

이름만 들으면 다 안다고 착각하기 쉬운 개념이다.

필자도 한참 동안 “시스템 프롬프트를 어딘가에 저장해두고 가져다 쓰는 것” 정도로만 알고 있었다.

막상 실측을 시작하려고 코드를 펴니까 캐시 마커를 어디에 붙여야 하는지, 캐시가 깨지는 조건이 뭔지, 가격이 어떻게 계산되는지가 머릿속에서 정리가 안 됐다.

그래서 실측에 들어가기 전에 한 번 정리한다.


1. 캐싱은 “이미 읽은 부분은 다시 읽지 마”라는 표시다

프롬프트 캐싱을 한 줄로 설명하라면 이렇게 말한다.

“매번 같은 문장을 다시 토큰으로 변환하고 모델에 통째로 넣는 짓을 그만하자.”

LLM API에 호출을 보낼 때마다 시스템 프롬프트 전체가 다시 토큰화되어 모델 안으로 들어간다.

같은 시스템 프롬프트라도 두 번째 호출이라고 더 싸지지 않는다.

10번 호출하면 같은 문장을 10번 토큰화해서 10번 비용을 낸다.

캐싱은 이 낭비를 막는 장치다.

“이 부분은 이미 처리해뒀으니 다시 계산하지 마”라고 모델에 알려주는 신호다.


2. 캐시되는 건 텍스트가 아니라 KV 행렬이다

여기서 주니어가 가장 자주 놓치는 부분이 있다.

캐시되는 건 “텍스트”가 아니라 transformer 내부의 KV 행렬이다.

LLM은 입력된 토큰마다 self-attention 연산을 위해 key와 value 벡터를 계산해둔다.

같은 prefix가 다시 들어오면 그 부분의 KV 벡터는 매번 똑같이 나온다.

낭비다. 그래서 한 번 계산해두고 잠시 보관한다.

다음 호출이 똑같은 prefix로 시작하면 그 부분의 KV 행렬을 메모리에서 꺼내 쓰고, 모델은 새로 추가된 부분만 계산한다.

이 구조에서 두 가지 함의가 나온다.

첫째, prefix가 정확히 일치해야 캐시가 먹는다. 한 토큰만 달라도 그 시점 이후의 KV는 다시 계산해야 한다.

둘째, 앞쪽일수록 캐시하기 좋다. 시스템 프롬프트나 reference document처럼 변하지 않는 큰 덩어리를 앞에 두고, 변하는 사용자 입력을 뒤에 두는 구조가 캐시 친화적이다.

이 직관 하나만 잡고 가면 나머지는 다 부수 효과다.


3. 가격은 “쓰기는 비싸고 읽기는 거의 공짜다”

Anthropic 기준으로 캐싱의 가격 구조는 이렇다.

항목가격 (입력가 대비)의미
Cache write (5분 TTL)1.25배캐시에 처음 쓸 때
Cache write (1시간 TTL)2배더 오래 보관할 때
Cache read0.1배 (10%)캐시에서 꺼내 쓸 때
Cache miss1배 (기본 입력가)캐시 안 맞으면 일반 호출

캐시 쓰기는 기본 입력가보다 비싸다.

캐시 읽기는 기본 입력가의 10%다.

그래서 손익분기는 단순한 산수로 나온다.

5분 TTL을 쓰는 경우:

1.25 + 0.1 × N < 1 × (N + 1)

이걸 풀면 N ≥ 2다.

세 번 이상 같은 prefix로 호출하면 그때부터 무조건 이득이다.

두 번 호출이면 본전, 한 번 호출이면 손해다.

여기서 자주 빠지는 함정이 있다.

“캐싱 켜두면 무조건 싸지는 거 아니에요?”

아니다. 캐시 쓰기 비용이 별도로 있기 때문에 재사용이 보장되지 않으면 오히려 비용이 늘어난다.

재사용 패턴이 명확한 곳에만 켜라.


4. 어디에 cache_control 마커를 붙이는가

Claude API에서 캐싱을 켜는 방법은 메시지 블록에 cache_control 마커를 다는 것이다.

messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": "<긴 시스템 프롬프트 또는 reference document>",
                "cache_control": {"type": "ephemeral"},
            },
            {
                "type": "text",
                "text": "<사용자 질문>",
            },
        ],
    }
]

직관에 어긋나는 점이 두 가지 있다.

첫째, 마커는 “여기까지를 캐시해” 라는 의미다. 마커가 붙은 블록 자체뿐 아니라 그 앞의 모든 시스템 프롬프트와 메시지가 함께 캐시된다.

둘째, 캐시 포인트는 최대 4개까지 둘 수 있다. 여러 단계로 쪼개서 잡으면 일부만 바뀌어도 앞쪽 캐시는 살릴 수 있다.

예를 들어 system → tool 정의 → reference document → 사용자 입력 순으로 배치하고 처음 세 블록에 각각 마커를 두면, reference document만 바뀌었을 때도 system과 tool 정의는 캐시가 살아남는다.

이 설계 감각이 캐싱의 진짜 노하우다.


5. usage 응답을 안 보면 캐시가 먹는지 모른다

캐싱을 켜둔다고 알아서 효과가 나는 게 아니다.

매 호출마다 응답 안의 usage 필드를 봐야 한다.

response = client.messages.create(...)
usage = response.usage

print(f"캐시 생성 토큰: {usage.cache_creation_input_tokens}")
print(f"캐시 읽기 토큰: {usage.cache_read_input_tokens}")
print(f"일반 입력 토큰: {usage.input_tokens}")
print(f"출력 토큰: {usage.output_tokens}")

이 네 가지를 따로 기록하지 않으면 캐시가 잘 먹는지 영원히 모른다.

지난 글에서도 비슷한 얘기를 했었다.

“input·output·cached·thinking을 따로 기록하지 않으면 어디서 비용이 새는지 못 본다.”

캐싱은 특히 그렇다.

cache_read_input_tokens가 0이면 캐시가 한 번도 안 먹은 것이고, cache_creation_input_tokens가 매 호출마다 큰 값이 잡힌다면 캐시를 매번 새로 쓰고 있다는 뜻이다.

전자는 prefix가 변하고 있다는 신호, 후자는 TTL이 너무 짧거나 호출 간격이 너무 길다는 신호다.


6. 캐시는 너무 쉽게 깨진다

캐시가 깨지는 조건은 생각보다 많다.

한 번씩은 다 밟고 가는 함정들이다.

  • 시스템 프롬프트 한 글자만 바뀌어도 그 시점 이후의 모든 KV가 새로 계산된다. 날짜를 prompt에 끼워넣지 마라.
  • 메시지 순서를 바꾸면 캐시가 깨진다. tool 정의 순서, reference 순서, few-shot 순서 모두 고정해야 한다.
  • tool 정의가 바뀌면 시스템 영역 캐시까지 영향을 받는다. tool 스키마를 자주 손대면 캐시 효과가 죽는다.
  • 5분 TTL을 넘기면 캐시가 만료된다. 호출 간격이 5분을 넘는 워크로드라면 1시간 TTL을 고려하거나 (2배 쓰기 비용을 감수하거나) 캐싱을 끄는 게 낫다.
  • 최소 토큰 수가 있다. 모델마다 다른데 Claude 3.5 Sonnet 기준 1024 토큰이 최소다. 짧은 시스템 프롬프트는 캐시 대상이 아니다.

이 다섯 가지를 머리에 넣어두면 “왜 캐시가 안 먹지?”의 90%는 자가 진단된다.


7. 어디에 쓰면 가장 큰 효과를 보는가

캐싱이 가장 효과적인 곳은 패턴이 분명하다.

길고, 변하지 않고, 자주 재사용되는 prefix가 있는 곳.

구체적으로 이런 워크로드다.

  • 긴 시스템 프롬프트 + 도구 정의 + few-shot이 있는 Agent
  • RAG에서 같은 문서를 여러 질의로 반복해 조회하는 케이스
  • 약관·규정·매뉴얼 같은 reference document를 매번 컨텍스트로 넣는 분류기
  • multi-turn 대화에서 직전 turn까지의 컨텍스트가 누적되는 구조

핀테크에서 다루는 작업으로 옮기면 더 와닿는다.

거래 메시지 분류를 한다고 하자.

시스템 프롬프트에 분류 라벨 정의, few-shot 20개, 회사 약관 발췌가 들어 있다면 입력 토큰이 쉽게 5천을 넘긴다.

이걸 캐싱 없이 하루 만 건 처리하면 같은 prefix를 만 번 토큰화해서 모델에 넣고 있는 셈이다.

캐시 한 번 잘 설계하면 두 번째 호출부터 그 부분의 입력 비용이 10%로 떨어진다.

반대로 캐싱이 손해를 보는 곳은 prefix가 매번 달라지거나 1회성인 호출이다.

  • 사용자 입력을 prompt 맨 앞에 두는 구조
  • 매번 다른 문서를 넣어야 하는 ad-hoc 분석
  • 하루에 한두 번 도는 배치 (5분 TTL이 의미 없음)

이런 워크로드에 캐싱을 켜두면 쓰기 비용만 누적되고 읽기는 안 일어나서 오히려 비용이 늘어난다.


다음 글에서 무엇을 할 것인가

여기까지가 캐싱을 처음 도입할 때 머리에 넣어둬야 하는 7가지다.

정리하면 이렇다.

  1. 캐싱은 “이미 처리한 부분 다시 처리하지 마”라는 표시
  2. 캐시되는 건 텍스트가 아니라 KV 행렬
  3. 쓰기는 1.25배, 읽기는 10%. 손익분기는 3회 재사용
  4. cache_control 마커는 “여기까지를 캐시해”의 뜻. 최대 4개
  5. usage의 cache_creation / cache_read를 따로 기록해야 효과 검증 가능
  6. 캐시는 한 글자, 순서, TTL, 토큰 수에 쉽게 깨진다
  7. 길고 변하지 않는 prefix가 자주 재사용되는 곳에만 켜라

다음 글에서는 이걸 실제로 돌려본다.

같은 시스템 프롬프트를 캐싱 없이 / 5분 TTL / 1시간 TTL로 각각 N번 호출하면서 누적 비용이 어떻게 갈라지는지 직접 측정한다.

이론으로는 “3회부터 이득”이 맞는데, 실측에서 그 손익분기가 정확히 어디에 있는지, 캐시 미스 분포는 어떻게 생겼는지가 진짜 보고 싶은 그림이다.

토큰값을 다섯 가지로 쪼갠 의미가 거기서 비로소 살아난다.

댓글 남기기