프롬프트 캐싱을 제대로 파고들었더니, 히트율은 목표가 아니었습니다#

시작은 “이게 대체 어디서 일어나는 거지"였습니다#
프롬프트 캐싱이 비용을 줄여준다는 건 알고 있었습니다. 같은 시스템 프롬프트를 반복해서 보낼 때 두 번째부터는 싸진다, 정도의 이해였습니다.
그런데 곰곰이 생각해 보니 이상했습니다. 캐싱이 모델 바깥의 어떤 중계 레이어에서 일어나는 거라면, 모델 입장에서는 절약되는 게 없어야 합니다. 그런데 절감 폭이 90%에 달한다고 합니다. 그렇다면 LLM(Large Language Model, 대규모 언어 모델) 사용 비용의 대부분이 모델 자체가 아니라 그 바깥 레이어에서 나온다는 뜻일까요.
파고들어 봤더니 전제부터 틀려 있었습니다. 그리고 그 과정에서 캐시 히트율을 높이는 것이 비용 최적화가 아니라는 더 불편한 사실도 알게 됐습니다. 이 글은 그 기록입니다.
먼저, 캐시되는 “KV 상태"가 무엇인가#
트랜스포머의 어텐션은 각 토큰마다 세 종류의 벡터를 만듭니다.
| 벡터 | 역할 | 비유 |
|---|---|---|
| Q(Query) | “나는 지금 무엇을 찾고 있나” | 검색어 |
| K(Key) | “나는 이런 내용을 담고 있다” | 문서의 색인 태그 |
| V(Value) | “그래서 실제로 전달할 내용은 이것” | 문서 본문 |
새 토큰을 하나 생성할 때, 그 토큰의 Q를 앞선 모든 토큰의 K와 비교해 관련도를 매기고, 그 가중치로 V들을 섞어서 다음 층으로 넘깁니다. 즉 K와 V는 “앞 토큰들이 뒤 토큰들에게 제공하는 참조 자료"입니다.
여기서 핵심은 K와 V가 그 토큰 자신과 그 앞 토큰들만으로 결정된다는 점입니다. 뒤에 무슨 토큰이 오든 이미 계산된 K · V는 변하지 않습니다. 반면 Q는 매번 새 토큰의 것만 필요하므로 보관할 이유가 없습니다.
그래서 모든 층(layer)의, 모든 토큰의 K와 V 텐서를 쌓아둔 것 — 이것이 KV 캐시입니다. 10만 토큰짜리 시스템 프롬프트라면 이 텐서 더미가 수 기가바이트에 달합니다. 프롬프트 캐싱은 이 텐서 더미를 버리지 않고 보관했다가 다시 붙여 쓰는 것입니다.
그리고 “1바이트만 바뀌어도 그 뒤가 다 깨지는” 이유도 여기서 나옵니다. 3번째 토큰이 바뀌면 3번째의 K · V가 바뀌고, 4번째 토큰은 그 바뀐 3번째를 참조해 계산되므로 4번째도 바뀌고, 연쇄적으로 끝까지 무효가 됩니다. 앞쪽은 멀쩡합니다. 캐시가 “접두사 단위"인 이유가 곧 인과적 어텐션의 구조 그 자체였습니다.
캐싱은 모델 밖이 아니라 모델 안에서 일어납니다#
이제 처음의 의문으로 돌아가 보겠습니다. 후보를 세 가지로 나눠 보면 명확해집니다.
| 무엇을 캐싱하나 | 어디에 | 이것이 프롬프트 캐싱인가 |
|---|---|---|
| 요청 → 응답 텍스트 매핑 | 앱 / 프록시 레이어 | 아니요. 흔한 오해이며, 이건 응답 캐싱입니다 |
| 토크나이즈 결과, 프롬프트 문자열 | 게이트웨이 | 아니요. 무의미할 만큼 저렴합니다 |
| 층별 K · V 텐서(모델 활성값) | 추론 서버의 GPU 메모리 계층 | 네, 이것입니다 |
즉 캐시 히트란 “모델이 그 구간의 순전파(forward pass)를 아예 돌리지 않는다” 는 뜻입니다. 임베딩부터 각 층의 QKV 투영, 어텐션, 피드포워드까지 이어지는 연산 사슬 전체를 건너뛰고, 저장된 K · V 텐서를 메모리에 되붙인 뒤 새로 들어온 뒷부분만 계산합니다.
캐시 규칙들이 전부 “모델 안” 사정으로 설명되는 것도 이 때문이었습니다.
- 모델을 바꾸면 캐시가 깨진다 — 다른 모델의 K · V 텐서는 차원도 의미도 다르므로 재사용 자체가 물리적으로 불가능합니다
- 최소 토큰 수가 모델마다 다르다 — 층 수와 헤드 구성이 다르니 캐시 항목 하나의 채산성 임계점이 다릅니다
- 첫 응답이 스트리밍을 시작해야 읽을 수 있다 — 텐서 쓰기가 실제로 완료돼야 하기 때문입니다
그렇다면 무엇이 절약되는가#
LLM 추론은 두 단계로 나뉩니다.
| 단계 | 하는 일 | 병목 | 특성 |
|---|---|---|---|
| Prefill | 입력 N토큰의 K · V를 한꺼번에 계산 | 연산(FLOPs) | 병렬 처리 가능, 연산 밀도 높음 |
| Decode | 출력 토큰을 하나씩 순차 생성 | 메모리 대역폭 | 병렬화 불가, 매 토큰마다 전체 가중치를 읽음 |
prefill 연산량은 대략 2 × 파라미터 수 × 입력 토큰 수입니다. 10만 토큰 프롬프트를 매번 다시 넣으면 매 요청마다 그만큼의 행렬 연산을 처음부터 다시 돌립니다. 캐싱은 이걸 통째로 없앱니다.
그러니 절약되는 것은 정확히 모델의 GPU 연산이었습니다. 필자의 원래 가정과는 정반대였습니다.
그럼 캐시 읽기의 0.1배는 무엇의 대가인가#
연산이 사라졌는데 왜 공짜가 아닐까요. 남는 비용이 있기 때문입니다.
- 저장 점유 — 수 기가바이트짜리 텐서를 TTL(Time To Live, 유효 기간) 동안 고속 메모리 계층에 붙들고 있어야 합니다. 그 공간은 다른 요청을 처리하는 데 쓸 수 없습니다
- 적재 대역폭 — 캐시를 다시 연산 메모리로 끌어오는 입출력 비용
- 잔여 연산 — 새로 들어온 뒷부분은 여전히 캐시된 K 전체와 어텐션을 계산해야 합니다
이 중 첫 번째가 지배적이라는 결정적 근거가 있습니다. TTL에 따라 쓰기 가격이 다르다는 사실입니다.
| 쓰기 | 상대 비용 |
|---|---|
| 5분 TTL | 1.25배 |
| 1시간 TTL | 2배 |
연산량은 두 경우가 완전히 동일합니다. 같은 프롬프트, 같은 prefill입니다. 다른 건 오직 “얼마나 오래 붙들고 있느냐"뿐인데 가격이 0.75배 차이납니다. 이 차액이 곧 저장 점유 비용이고, 뒤집어 말하면 1.25배 중 1.0배가 연산, 0.25배가 5분치 보관료라는 뜻입니다.
정리하면, 토큰 비용은 대략 GPU 연산 + 메모리 대역폭 + 메모리 점유이고 전부 모델 실행 그 자체의 비용입니다. 프롬프트 캐싱이 90%를 깎을 수 있는 건 바깥 레이어가 비싸서가 아니라, prefill 연산이 원래 그만큼 컸고 그게 순수한 중복이었기 때문입니다.
단 하나의 규칙: 접두사 일치#
여기까지 이해하고 나면 캐싱의 모든 규칙이 한 문장으로 압축됩니다.
캐싱은 접두사 매칭이다. 프리픽스 안 어디든 1바이트라도 바뀌면 그 지점 이후 전부 무효화된다.
그리고 요청이 프롬프트로 조립되는 물리적 순서는 고정되어 있습니다.
tools → system → messagestools가 위치 0에 놓입니다. 도구를 하나 추가하거나 순서를 바꾸면 시스템 프롬프트와 전체 대화 이력의 캐시가 통째로 날아갑니다. 반대로 마지막 system 블록에 캐시 경계를 두면 tools와 system이 함께 캐시됩니다.
“breakpoint를 찍는다"가 실제로 뜻하는 것#
용어부터 정리하면, 마커 · breakpoint · 캐시 경계는 전부 같은 말이고, 실체는 요청 JSON의 특정 블록에 붙이는 cache_control 필드입니다.
여기서 가장 큰 오해가 생깁니다.
- 틀린 이해: “이 블록을 캐시해라” (블록 하나만 저장)
- 맞는 이해: “프롬프트 처음부터 이 블록 끝까지를 하나의 캐시 항목으로 저장해라”
즉 경계 표시이고, 표시된 지점까지의 누적분 전체가 저장됩니다. 여섯 개의 블록으로 이루어진 프롬프트에서 네 번째 블록에 cache_control을 붙였다고 해보겠습니다.
| 순서 | 블록 | 캐시 항목에 포함되나 |
|---|---|---|
| 1 | tools | 포함 |
| 2 | system | 포함 |
| 3 | 블록1 | 포함 |
| 4 | 블록2 ← cache_control | 포함 — 여기가 경계 |
| 5 | 블록3 | 제외, 매번 새로 계산 |
| 6 | 블록4 | 제외, 매번 새로 계산 |
cache_control을 붙인 네 번째 블록 하나만 저장되는 것이 아닙니다. 1번부터 4번까지가 통째로 묶여 하나의 캐시 항목이 됩니다. 그리고 다음 요청에서 1번부터 4번까지가 바이트 단위로 똑같으면, 그 전체를 0.1배에 재사용하고 5번과 6번만 새로 계산합니다.
같은 문서에 질문을 세 번 던지는 상황으로 비교해 보겠습니다. 마커를 프롬프트 맨 끝에 찍으면 이렇게 됩니다.
요청 1: [문서][질문A ★] → "문서+질문A"를 저장 (1.25배 지불)
요청 2: [문서][질문B ★] → 프리픽스가 다름 → 미스, 또 저장
요청 3: [문서][질문C ★] → 또 미스, 또 저장
결과: 세 번 다 정가보다 비싸고, 읽기는 0회. 순손실입니다.마커를 공유 구간 끝으로 옮기면 이렇게 바뀝니다.
요청 1: [문서 ★][질문A] → "문서"까지 저장 (1.25배)
요청 2: [문서 ★][질문B] → ★까지가 동일 → 히트 (0.1배)
요청 3: [문서 ★][질문C] → 히트 (0.1배)프롬프트 내용은 두 경우가 완전히 같습니다. 차이는 마커를 문서 뒤에 놓았느냐, 질문 뒤에 놓았느냐뿐입니다.
한 문장 규칙으로 하면 이렇습니다. 마커는 “여기까지는 다음 요청에도 똑같을 것"이라고 자신할 수 있는 마지막 지점에 찍습니다.
참고로 이 캐싱은 옵트인(opt-in, 명시적으로 켜야 동작하는 방식) 입니다. cache_control을 넣지 않으면 아무리 큰 시스템 프롬프트를 반복해서 보내도 캐싱은 일어나지 않습니다. 최상위에 cache_control을 넣는 “자동 캐싱” 방식도 있는데, 이건 자동 활성화가 아니라 자동 배치입니다. 필드 자체는 넣어야 합니다.
그래서 프롬프트를 지층으로 설계합니다#
캐싱 최적화는 마커를 잘 찍는 기술이 아니라 레이아웃 설계였습니다. 프롬프트를 변경 주기가 다른 지층의 퇴적물로 보고, 앞쪽일수록 단단하게 쌓으면 됩니다.
| 층 | 내용 | 변경 주기 | 캐시 경계 |
|---|---|---|---|
| 1 (맨 앞, 가장 단단함) | tools | 배포마다만 | 경계 1 |
| 2 | system | 배포마다만 | 경계 2 |
| 3 | 문서 / RAG | 세션 단위 | 경계 3 |
| 4 | 대화 이력 | 턴 단위, append-only | 경계 4 |
| 5 (맨 뒤, 가장 물렁함) | 이번 질문 | 요청마다 | 마커 없음 |
실행 지침은 전부 이 그림에서 파생됩니다. 효과가 큰 순서로 몇 가지만 추리면 이렇습니다.
1. 시스템 프롬프트를 빌드 타임 상수로 취급합니다. 런타임에 문자열 보간으로 조립하지 않습니다. 현재 시각, 사용자 이름, 세션 ID 같은 값을 시스템 프롬프트에 넣는 순간 히트율이 영구히 0이 됩니다. 이 하나가 0%와 90%를 가르는 경우가 가장 흔했습니다.
2. 도구 목록을 전 사용자 공통으로 고정합니다. tools는 위치 0이라 여기가 갈라지면 회복 수단이 없습니다. 권한별로 목록을 나누지 말고 전체를 모두에게 주되, 권한 검사는 실행 핸들러에서 합니다. 권한 없는 호출은 오류 결과로 거절하면 모델이 알아서 우회합니다.
3. 직렬화를 결정적으로 만듭니다. 캐시 키는 렌더링된 바이트입니다. JSON 키 순서가 흔들리면 논리적으로 같은 내용도 다른 프롬프트가 됩니다. sort_keys 옵션을 습관화하고, 검색 결과 같은 것도 문서 ID 기준으로 정렬해 삽입합니다.
4. 대화 이력은 append-only로 다룹니다. 이력 중간을 편집하거나 잘라내면 그 지점 이후가 전부 무효화됩니다. 20만 토큰 대화에서 3번째 턴을 손대면 19만 토큰이 정가로 재계산됩니다. 모드 전환 같은 것도 시스템 프롬프트를 고쳐 쓰는 대신, 최신 모델에서 지원하는 system 역할 메시지를 이력 뒤에 덧붙이는 편이 낫습니다.
5. RAG(Retrieval-Augmented Generation, 검색으로 찾은 자료를 프롬프트에 붙여 답하게 하는 방식)는 고정 문서와 쿼리별 검색 결과를 분리 배치합니다. 검색 결과를 앞쪽에 넣으면 쿼리가 바뀔 때마다 그 뒤가 전부 무효화됩니다. 검색 결과는 반드시 맨 뒤에 둡니다.
6. 서브에이전트와 포크는 부모의 프리픽스를 그대로 복사합니다. 요약이나 판정 같은 부수 호출에서 시스템 프롬프트를 “가볍게” 새로 만들고 싶은 충동이 들지만, 부모 캐시를 못 타면 그게 순증입니다.
다만, 다 깨지는 건 아닙니다#
무효화에는 계층이 있어서, 변경은 자기 계층과 그 아래만 무효화합니다. 이걸 정확히 알면 불필요한 회피 설계를 하지 않게 됩니다.
| 변경 내용 | tools | system | messages |
|---|---|---|---|
| 도구 정의, 모델 | 무효 | 무효 | 무효 |
| 웹검색 · 인용 토글 | 유지 | 무효 | 무효 |
| 시스템 프롬프트 내용 | 유지 | 무효 | 무효 |
tool_choice, 이미지 첨부 | 유지 | 유지 | 무효 |
| 메시지 내용 | 유지 | 유지 | 무효 |
걱정할 것은 도구 정의 변경과 모델 교체 둘뿐입니다.
TTL은 트래픽 간격의 함수이고, 갱신은 무료입니다#
실무에서 가장 저평가되는 사실이 이것입니다. TTL은 마지막 히트 시점부터 다시 카운트되고, 그 갱신에는 비용이 없습니다. 5분 안에 계속 요청이 들어오는 프리픽스는 한 번만 쓰고 사실상 영원히 살아 있습니다.
| 요청 간격 | 선택 |
|---|---|
| 5분 미만 | 기본 5분 TTL — 무료 갱신으로 계속 유지됨 |
| 5분 ~ 60분 | 1시간 TTL — 5분짜리는 매번 만료되어 재작성 비용 발생 |
| 60분 초과 | 캐싱 의미 없음 |
그래서 “프리픽스 종류를 줄이는” 설계가 복리로 작용합니다. 프리픽스가 8종에서 1종으로 줄면 각 프리픽스의 요청 간격이 8분의 1이 되고, 그 결과 무료 갱신 구간에 안정적으로 진입합니다.
코딩 에이전트를 쓸 때는 신경 쓸 필요가 없습니다#
여기까지가 API를 직접 호출할 때의 이야기입니다. 그렇다면 Claude Code나 Cursor 같은 코딩 에이전트로 대화할 때는 어떨까요.
결론부터 말하면 신경 쓸 필요가 없습니다. 요청을 조립하는 주체가 사용자가 아니라 하네스이므로, 캐시 경계를 붙이는 것도 하네스의 일입니다. 그리고 이건 선택 사항이 아닙니다. 에이전트는 매 턴 전체 이력을 다시 보내야 하니, 캐싱 없이는 대화가 길어질수록 비용이 감당 불가능해집니다. Claude Code 팀의 회고 제목이 아예 “프롬프트 캐싱이 전부다"인 것도 그래서일 것입니다.
이하 구체적인 동작은 Claude Code 기준으로 정리했습니다. 세부는 도구마다 다르겠지만, 흥미로운 건 Claude Code가 쓰는 구조가 앞서 정리한 지층 모델과 정확히 같다는 점이었습니다.
| 층 | 내용 | 언제 바뀌나 |
|---|---|---|
| 시스템 프롬프트 | 핵심 지시, 도구 정의, 출력 스타일 | 도구 정의가 바뀌거나 Claude Code 업그레이드 |
| 프로젝트 컨텍스트 | CLAUDE.md, 오토 메모리, 규칙 | 세션 시작, /clear, /compact |
| 대화 | 메시지, 응답, 도구 결과 | 매 턴 |
Claude Code는 TTL도 인증 방식에 따라 알아서 정합니다. Claude 구독이면 1시간을 자동으로 요청하므로 잠시 자리를 비워도 캐시가 살아 있고, API 키나 클라우드 제공사 경유면 5분이 기본입니다.
다만 캐시를 깨는 행동은 알아둘 만합니다#
자동이라고 해서 사용자 행동과 무관한 건 아니었습니다. 아래 행동들은 다음 한 턴을 느리고 비싸게 만듭니다.
| 행동 | 이유 |
|---|---|
/model 전환 | 모델마다 별도 캐시 — 내용이 같아도 전체 재계산 |
opusplan 설정 사용 | 플랜 모드를 토글할 때마다 모델이 전환됨 |
/effort 변경 | effort도 캐시 키의 일부 |
| fast mode 켜기 | 요청 헤더가 캐시 키에 포함 — 세션 초반에 켜는 편이 저렴 |
| 도구 전체 거부 규칙 추가 | Bash 같은 맨 이름을 거부하면 컨텍스트에서 제거됨 |
/compact | 이력을 요약본으로 치환 |
| 업그레이드 후 긴 세션 재개 | 시스템 프롬프트가 바뀌어 이력 전체가 새 프리픽스 뒤로 |
반대로 캐시를 깨지 않는 행동도 정리해 두면 유용합니다. 저장소 파일 편집, CLAUDE.md 편집, 출력 스타일 변경, 권한 모드 전환, 스킬 · 커맨드 호출, /recap, 서브에이전트 스폰은 모두 안전합니다. 다만 CLAUDE.md와 출력 스타일은 캐시가 안 깨지는 대신 변경 내용도 적용되지 않습니다. 세션 시작 시 읽어 메모리에 유지되기 때문이고, /clear나 재시작 후에 반영됩니다.
특히 유용한 대비는 /compact와 /rewind입니다. 잘못된 경로로 갔다면 /compact가 아니라 /rewind 를 쓰는 게 낫습니다. rewind는 이미 캐시된 프리픽스로 잘라내지만, compaction은 새 프리픽스를 만들기 때문입니다.
또 하나 몰랐던 사실은 캐시 범위가 머신 + 디렉토리 단위라는 점이었습니다. 시스템 프롬프트에 작업 디렉토리, 플랫폼, 셸, 운영체제 버전이 박혀 있어서, 다른 디렉토리의 세션끼리는 캐시를 공유하지 못합니다. 같은 저장소의 worktree도 별개입니다.
다른 도구를 쓴다면#
위의 명령어 이름들은 Claude Code 고유입니다. 하지만 캐시가 깨지는 원인 자체는 하네스와 무관하게 같습니다. 어떤 코딩 에이전트를 쓰든 아래 세 가지는 프리픽스를 무너뜨립니다.
- 모델 교체 — 캐시는 모델 단위로 격리되므로 예외가 없습니다
- 도구 세트 변경 — 도구 정의는 프롬프트 맨 앞에 놓입니다
- 이력 재작성 — 요약, 압축, 중간 편집은 모두 접두사를 바꿉니다
쓰는 도구에서 이 세 가지에 해당하는 동작이 무엇인지 찾아보면, 그게 곧 그 도구의 “비싼 행동” 목록입니다.
그리고 함정: 히트율은 목표가 아니었습니다#
여기까지는 “히트율을 올리면 된다"는 이야기였습니다. 그런데 압축(compaction)을 언제 할지 따져보다가 이상한 결과를 만났습니다.
압축은 이력을 요약본으로 치환하므로 캐시를 파괴합니다. 그러니 압축을 덜 해서 히트율을 높게 유지하는 게 비용상 유리해 보입니다. 실제로 히트율만 보면 그렇습니다.
| 이력 | 새 턴 | 히트율 | |
|---|---|---|---|
| 압축 안 함 | 300K 읽기 | 5K 쓰기 | 98.4% |
| 압축 함 | 30K 읽기 | 5K 쓰기 | 85.7% |
압축한 쪽이 12%포인트 낮습니다. 그런데 실제 요금을 계산해 보면 결과가 뒤집힙니다. (Claude Opus 5 기준, 입력 정가 100만 토큰당 5달러 · 캐시 읽기 0.5달러 · 캐시 쓰기 6.25달러)
| 캐싱 없다면 | 캐싱 있으면 | 캐싱 절약률 | |
|---|---|---|---|
| 압축 안 함 (300K) | $1.525 | $0.181 | 8.4배 |
| 압축 함 (30K) | $0.175 | $0.046 | 3.8배 |
압축 안 한 쪽이 히트율도 높고 캐싱 절약률도 두 배 이상 좋은데, 청구서는 4배 비쌉니다.
왜 이런 일이 벌어지는가#
0.1배 할인은 양쪽에 똑같이 적용되기 때문입니다.
| 이력 크기 | 0.1배 적용 후 | |
|---|---|---|
| 압축 안 함 | 300K | 30K 상당 |
| 압축 함 | 30K | 3K 상당 |
| 여전히 10배 차이 |
할인율이 아무리 좋아도 300K와 30K의 비율은 그대로 10 대 1입니다. 곱셈 상수는 두 값을 나란히 끌어내릴 뿐, 대소 관계를 뒤집지 못합니다. 0.1배는 할인이지 상한이 아니었습니다.
문제의 뿌리는 히트율이 비율이고 비용은 절댓값이라는 데 있습니다.
히트율 = 캐시 읽기 / (캐시 읽기 + 쓰기 + 정가입력)분자를 키우면 히트율이 오르는데, 분자를 키우는 방법이 “이력을 크게 유지하는 것"입니다. 즉 히트율을 극대화하는 가장 확실한 방법이 곧 가장 비싼 선택입니다. 900K 이력을 안고 도는 에이전트의 히트율은 99.5%이고, 요청 하나가 0.45달러입니다.
그럼 히트율은 언제 보나#
지표가 유효한 조건을 구분하면 정리됩니다.
| 상황 | 히트율이 | 이유 |
|---|---|---|
| 프롬프트 크기가 고정된 상태에서 비교 | 유효한 지표 | 같은 프롬프트가 캐시되냐 마느냐의 문제 |
| 프롬프트 크기 자체가 변수 | 오도하는 지표 | 분모가 함께 움직여 방향이 뒤집힘 |
앞서 정리한 지침 중 시스템 프롬프트 동결, 직렬화 결정성, 도구 고정, 서브에이전트 프리픽스 복사는 전부 첫 번째 범주입니다. 똑같은 내용을 캐시되게 만드는 것이라 순수 이득이고, 히트율이 그대로 성과 지표가 됩니다.
반면 압축 정책, RAG 전략, 세션 길이 결정은 두 번째 범주입니다. 컨텍스트 총량을 조절하는 결정이라 히트율이 아니라 요청당 절대 비용으로 봐야 합니다.
히트율은 “낭비를 잡아내는 진단 지표"이지 “최대화할 목표"가 아니었습니다. 히트율이 낮으면 프리픽스가 흔들리고 있다는 신호이고, 히트율이 높으면 문제가 없다는 뜻이지 최적이라는 뜻은 아닙니다.
압축의 진짜 판단 기준#
그렇다면 압축 여부는 무엇으로 판단해야 할까요. 히트율이 아니라 압축 이벤트 1회 비용 대비 이후 절감의 누적입니다.
| 항목 | 금액 |
|---|---|
| 압축 1회 비용 (요약 호출 + 새 프리픽스 쓰기) | 약 $1.09 |
| 요청당 절감액 ($0.181 → $0.046) | $0.135 |
| 손익분기 | 약 8턴 |
압축 후 8턴 이상 이어질 세션이면 압축이 이득이고, 그 전에 끝날 세션이면 순손실입니다. 누적으로 보면 차이가 더 분명해집니다. 압축하지 않으면 요청당 비용이 선형으로 증가하므로 N턴 누적 비용이 N제곱에 비례하지만, 압축하면 톱니 모양으로 리셋되어 N에 대략 비례합니다. 세션이 길수록 격차가 가속도로 벌어집니다.
덧붙여, 캐시는 처리량을 아껴주지 않습니다#
돈으로 잡히지 않는 비용도 있습니다. 캐시 읽기 토큰도 분당 처리량 한도에는 100% 그대로 계산됩니다. 0.1배는 요금에만 적용됩니다.
분당 200만 토큰 한도에서 90만 토큰 이력을 안고 돌면 분당 두 요청이 상한입니다. 히트율은 99.5%로 완벽해 보이는데 에이전트는 대기하고 있습니다. 이 경우 압축은 비용 최적화가 아니라 처리량 확보 수단입니다.
여기에 응답 지연과 컨텍스트 드리프트까지 더하면, 압축을 무한정 미루는 전략은 세 방향에서 대가를 치릅니다.
정리하며#
파고들기 전과 후로 나눠 보면 이렇게 정리됩니다.
| 파고들기 전 | 파고든 후 |
|---|---|
| 캐싱은 모델 밖 어딘가에서 일어난다 | 모델 내부의 K · V 텐서를 재사용하는 것이다 |
| 절약되는 건 부수적 오버헤드다 | 절약되는 건 정확히 GPU 연산(prefill)이다 |
cache_control은 “이 블록을 캐시해라” | “처음부터 여기까지를 한 항목으로 저장해라” |
| 히트율을 올리는 게 비용 최적화다 | 히트율은 진단 지표일 뿐 목표가 아니다 |
| 캐싱하면 컨텍스트를 키워도 괜찮다 | 0.1배는 할인이지 상한이 아니다 |
그리고 실무 지침으로 압축하면 두 문장이 남습니다.
첫째, 캐싱 최적화는 “무엇을 캐싱할까"가 아니라 “무엇을 앞에 둘까"의 문제입니다. 프롬프트를 변경 주기에 따라 층으로 쌓고 그 층서를 깨는 코드를 막으면, 마커 배치는 거의 자동으로 따라옵니다.
둘째, 캐싱은 “얼마나 아꼈나"를 개선하고, 컨텍스트 총량은 “얼마를 내는가"를 결정합니다. 두 개는 곱해지는 관계이고, 최종 금액은 후자가 지배합니다.
캐싱 하나를 파고들었을 뿐인데, 결국 컨텍스트를 어떻게 설계하고 세션을 어떻게 운용할 것인가라는 더 큰 질문으로 이어졌습니다. 어쩌면 그게 자연스러운 순서였는지도 모르겠습니다.
참고 자료