7월 초 무신사 테크블로그에 LLM 비용 64% 절감, 캐시 히트율 98% 달성기라는 글이 올라왔다. 29CM Pricing 팀이 AWS Bedrock 위의 상품 속성 추출 파이프라인에 Prompt Caching을 적용한 기록인데, 국내 테크블로그에서 캐싱 실측 수치를 공개한 글은 이게 사실상 처음이다. SK플래닛이 작년에 Bedrock 캐싱 도입기를 쓴 적은 있지만 절감률도 히트율도 공개하지 않았다.
숫자만 옮기면 이렇다. 상품 속성 추출 단일 API가 전체 LLM 토큰의 92%를 차지하고 있었고, 요청 구조는 매번 똑같은 15K 토큰 시스템 프롬프트에 2K 토큰짜리 상품 데이터가 붙는 형태였다. 고정부와 변동부를 분리하고 캐시 포인트를 찍자 일주일 실측으로 히트율 98%, 전체 청구액 기준 64% 절감.
좋은 실전기지만 이 글을 “캐싱 켜면 싸진다”로 요약하고 덮으면 제일 중요한 걸 놓친다. 주목할 것은 히트율이라는 숫자의 성격이다. 캐시 히트율은 청구서에 붙는 할인 항목이 아니라 내 프롬프트가 구조적으로 잘 설계됐는지에 대한 채점 결과다. 매 요청마다 갱신되고, 원인 없이는 절대 낮아지지 않고, 부분 점수가 없는 성적표다. 그리고 뒤에서 보겠지만, 에이전트 시대에 이 성적표는 프로덕션 LLM 시스템의 1번 지표가 되는 중이다.
토큰 하나가 체인을 끊는다
원리부터. 트랜스포머는 처리한 토큰마다 어텐션의 Key·Value 텐서를 계산해 두는데, 이 KV 캐시는 “앞의 토큰들이 이러이러했을 때”라는 조건 아래 만들어진 값이라 앞이 바뀌면 재사용할 수 없다. 서빙 엔진은 이 성질을 캐시 키에 그대로 반영한다. vLLM의 prefix caching 설계에서는 각 KV 블록의 해시를 그 블록의 토큰과 앞선 prefix 전체로부터 계산한다. 그래서 재사용은 항상 앞에서부터 이어지는 prefix 단위로만 가능하고 중간의 토큰 하나가 바뀌면 뒤따르는 블록들의 해시 체인이 전부 끊긴다. 프롬프트 뒤쪽을 아무리 그대로 둬도 소용없는 이유다.
API 레벨의 규칙은 이 물리적 성질의 직역이다. Anthropic 문서 기준으로 요청은 tools → system → messages 순으로 렌더되고, 캐시 히트는 브레이크포인트까지의 100% 동일한 프리픽스를 요구한다. 앞 계층이 바뀌면 뒤 계층 캐시는 전부 무효다. 툴 정의를 하나 고치면 시스템 프롬프트와 대화 이력 캐시까지 통째로 날아가고 모델을 바꾸면 캐시는 아예 다른 장부가 된다(캐시는 모델 단위다).
이 규칙이 히트율을 성적표로 만든다. 히트율이 낮다는 것은 운이 나쁜 게 아니라 프롬프트에서 변하는 무언가가 앞쪽에 있다는 뜻이고, 그 무언가는 반드시 존재한다. 시스템 프롬프트에 박힌 타임스탬프, 사용자 ID, 정렬을 강제하지 않은 JSON 직렬화, 요청마다 달라지는 툴 목록. 고약한 것은 채점이 조용히 이뤄진다는 점이다. 캐시가 안 걸려도 API는 에러를 던지지 않고 cache_read_input_tokens가 0으로 찍힐 뿐이다. 최소 캐시 토큰(모델별 512~4,096)에 미달해도 마찬가지로 조용히 지나간다. 성적표를 열어보지 않는 학생에게는 낙제도 통보되지 않는다.
29CM의 답안지
무신사 글이 실전기로서 잘 쓰인 지점은, 캐싱을 옵션 한 줄이 아니라 이 구조 문제로 다뤘다는 데 있다. 순서가 교과서적이다.
첫째, 가시성부터 만들었다. Bedrock 콘솔은 일별 토큰 합계만 보여줘서 어떤 API가 비용을 만드는지 알 수 없었고, 팀은 LangChain4j의 ChatModelListener와 서블릿 필터로 API별·토큰종류별(input/output/cache_read/cache_write) 메트릭을 Prometheus에 쌓는 것부터 시작했다. 이 대시보드가 켜진 뒤에야 92%짜리 범인이 보였다.
둘째, 캐시 포인트를 찍기 전에 프롬프트를 분리했다. 기존 코드는 고정값(추출 규칙·예시·스키마)과 변동값(상품 데이터)의 경계가 흐릿했다. 고정값을 시스템 프롬프트 블록으로, 변동값을 유저 메시지로 옮기는 재구성이 먼저였고 캐시 마커는 그 다음이었다. 캐싱 적용의 실제 작업량은 대부분 여기에 있다.
셋째, 예측과 실측을 대조했다. 적용 전에 호출 패턴·가격표·실측 토큰 분포를 입력으로 비용 시뮬레이션을 돌렸는데, 시나리오 구성까지만 LLM에 맡기고 최종 금액 계산은 결정론적 경로(실측 토큰 × 단가)에 뒀다. 시뮬레이션이 산출한 히트율 예측과 실측이 1%p 차이로 일치했다. 이건 팀의 운이 아니라 캐싱이라는 메커니즘의 성질이다. 호출 패턴이 고정되면 히트율은 확률이 아니라 산수다. 성적표 비유를 다시 쓰면, 이 시험은 출제 범위가 완전히 공개돼 있다.
부록 같은 디테일 하나. 작업 당시 LangChain4j의 Bedrock 모듈에는 캐시 TTL 파라미터가 없어 5분 기본값만 쓸 수 있었고, 팀은 SDK를 직접 호출해 우회하면서 cacheTtl 파라미터를 추가하는 PR을 업스트림에 올렸다. 저자의 첫 기여였고 머지됐다. 글에는 “다음 릴리즈를 기다리는 중”이라고 적혀 있지만 확인해 보니 1.14.0에 이미 들어가 있다. 비용 최적화 과정에서 생긴 결핍이 오픈소스 기여로 되돌아간, 이 글에서 제일 보기 좋은 대목이다.
같은 시험을 먼저 친 답안지들
이 성적표의 채점 기준이 얼마나 보편적인지는 다른 답안지들을 나란히 놓으면 선명해진다.
7%를 84%로 만든 ProjectDiscovery. 보안 테스트 에이전트 Neo를 만드는 이 팀의 4월 글은 지금까지 공개된 것 중 가장 단단한 프로덕션 실측이다. 20K 토큰이 넘는 시스템 프롬프트를 쓰는데 히트율이 7%에 머물러 있었다. 범인은 working memory와 런타임 컨텍스트 같은 매 스텝 바뀌는 값들이 프롬프트 중간에 끼어 있던 것. 이 동적 섹션들을 대화 맨 끝의 유저 메시지로 옮기자 히트율이 하룻밤 만에 74%로 뛰었고 이후 84%에 안착했다. 시스템 프롬프트는 실제 값 대신 템플릿 플레이스홀더를 그대로 둬 “모든 유저, 모든 스레드, 모든 날짜에 걸쳐 byte-identical”하게 만들었고, 덕분에 동시 사용자들이 캐시 하나를 공유한다. 결과는 비용 59% 절감, 누적 98억 토큰 캐시 처리. 1,225스텝짜리 극단 태스크에서 히트율 91.8%를 찍었는데, 최적화 전 같은 규모 태스크(히트율 3.2%)보다 60배 쌌다.
Thomson Reuters Labs가 짚은 병렬화의 역설. 이쪽 글은 프로덕션 실적이 아니라 30K 토큰 문서를 가정한 워크드 예제라는 점을 먼저 적어두지만 짚는 함정은 실전적이다. 캐시 엔트리는 첫 응답이 스트리밍을 시작한 뒤에야 읽을 수 있다. 그래서 같은 문서에 질문 10개를 동시에 발사하면 전부가 서로의 캐시를 못 읽고 풀 프라이스를 낸다. 측정된 히트율 4.2%, 사실상 0이다. 처방은 워밍 콜 1회를 동기로 먼저 보내고(캐시 적재 2~4초) 나머지를 발사하는 것. 속도를 위한 병렬화가 비용을 곱절로 만드는 역설이고, 무신사 글이 안티패턴 목록에 “병렬 요청 동시 발사”를 넣은 것과 같은 항목이다.
표현은 제각각이지만 규칙은 하나로 수렴한다. 변경 빈도가 낮은 것을 앞에, 높은 것을 뒤에. Manus는 “시스템 프롬프트 첫 줄에 초 단위 타임스탬프를 넣지 마라”라고 쓰고, Anthropic은 “시스템 프롬프트를 고치지 말고 다음 턴 메시지로 전달하라”라고 쓰고, ProjectDiscovery는 “working memory를 꼬리로 옮겨라”라고 쓴다. 전부 같은 문장이다.
에이전트가 이 숫자를 1번 지표로 만들었다
캐싱 자체는 2024년부터 있던 기능이다. 그런데 왜 2025~2026년에 와서 히트율이 이렇게 대접받는가. 에이전트가 워크로드의 모양을 바꿨기 때문이다.
에이전트는 툴을 부를 때마다 액션과 관찰 결과를 컨텍스트에 덧붙이고 전체를 다시 보낸다. Manus를 만드는 Yichao ‘Peak’ Ji는 2025년 7월 글에서 Manus의 평균 input:output 토큰 비율이 100:1이라고 공개하며 이렇게 썼다. “단 하나의 지표만 골라야 한다면, KV 캐시 히트율이 프로덕션 단계 AI 에이전트의 가장 중요한 지표라고 주장하겠다.” 올해 나온 에이전트 워크로드 계측 연구는 이 직관을 숫자로 확인했다. 에이전트의 raw input:output 비율은 53.9배에서 559.8배에 이르지만 매 턴 실제로 새로 추가되는 증분은 출력의 1.5~7.3배에 불과하다. 입력의 대부분이 이전 턴의 재전송이라는 뜻이고, 그래서 이론적 캐시 히트율이 87.9~99.3%에 달한다. 캐싱 없는 에이전트는 같은 책을 매 턴 처음부터 다시 읽는 셈이다.
제공자 쪽도 같은 말을 한다. Claude Code 팀은 4월에 “Prompt caching is everything”이라는 제목의 글에서 캐시 히트율에 알람을 걸어두고 너무 낮아지면 장애(SEV)를 선언한다고 밝혔다. “업타임을 모니터링하듯 캐시 히트율을 모니터링하라”는 문장과 함께. 히트율 몇 %p가 비용과 지연을 극적으로 바꾸기 때문인데, 실제로 Anthropic 공식 수치 기준 캐시는 비용 최대 90%와 함께 지연도 최대 85% 줄인다. 100K 토큰 문서와의 대화에서 첫 토큰까지 11.5초가 2.4초로 준다. 히트율은 비용 지표이기 전에 UX 지표다.
학술 검증도 붙었다. PwC 연구진의 Don’t Break the Cache(2026년 1월)는 OpenAI·Anthropic·Google 3사 캐싱을 500개 이상의 에이전트 세션에서 횡단 평가해 비용 41~80% 절감, 첫 토큰 지연 13~31% 개선을 측정했다. 반전도 하나 있다. 아무 생각 없이 전체 컨텍스트를 캐시하는 naive 전략은 오히려 지연을 늘릴 수 있다. 동적 콘텐츠를 끝으로 보내고 세션 고유의 툴 결과를 캐시 경계 밖에 두는 전략적 경계 제어가 일관되게 이긴다. 켜는 것만으로는 부족하고 설계해야 한다는 것, 이 글의 테제를 논문이 그대로 말하고 있다.
시간순으로 놓으면 수렴이 보인다. 2025년 7월 Manus가 명제를 던졌고, 2026년 1월 논문이 3사 횡단으로 검증했고, 4월에 제공자(Anthropic)와 사용자(ProjectDiscovery)가 각자의 자리에서 확증했고, 7월에 국내 실측(29CM)이 합류했다. 1년 만에 에이전트 비용·지연 최적화의 1번 지표라는 합의가 만들어진 셈이다.
내 성적을 올리는 순서
사례들을 처방 순서로 재조립하면 이렇다.
1. 계측 없이 시작하지 않는다. 응답의 usage 필드(cache_read_input_tokens, cache_creation_input_tokens)를 API별로 쪼개 쌓는 것부터. 총 입력 = input + cache_read + cache_creation이므로 히트율 계산도 여기서 나온다. 캐시 실패는 조용하다. 대시보드 없이는 낙제를 통보받지 못한다.
2. 고정/변동 분리가 본 작업이다. 시스템 프롬프트를 열어 호출마다 같은 것과 바뀌는 것을 갈라낸다. 타임스탬프·사용자 ID·요청 ID는 유저 메시지로, JSON 직렬화는 키 정렬 강제, 툴 목록은 순서 고정. 앞이 바뀌면 뒤가 전부 죽는다는 규칙 하나에서 전부 도출된다.
3. TTL은 호출 간격으로 고른다. 손익분기가 명확하다. Anthropic 기준 캐시 쓰기는 5분 TTL이 기본 단가의 1.25배, 1시간 TTL이 2배이고 읽기는 0.1배라, 5분 캐시는 두 번째 요청에서 본전이고 1시간 캐시는 세 번은 읽어야 남는다. TTL은 히트마다 추가 비용 없이 리셋되므로, 호출 간격이 5분보다 짧으면 5분 캐시로 충분하다. 29CM처럼 배치 사이 간격이 있는 워크로드가 1시간 TTL의 자리다. Bedrock에서는 1시간 TTL 지원이 모델별로 다르니(Claude Opus 4.6·Sonnet 4.6은 아직 5분만) 문서 확인이 필요하다.
4. 병렬 발사 전에 워밍 콜. 캐시는 첫 응답이 시작된 뒤에야 읽힌다. 동시 발사는 전원 미스다.
5. 히트율을 업타임처럼 다룬다. 알람을 걸고 떨어지면 배포를 의심한다. Claude Code 문서가 열거하는 캐시를 깨는 행위 목록이 좋은 참고다. 모델 전환, effort 레벨 변경, MCP 서버 토글까지 캐시 키의 일부다. #44에서 모델 교체 비용을 이야기했는데, 캐시 전량 무효화는 그 청구서에 추가할 항목이다.
제공자별 정책은 생각보다 갈린다. 요약하면:
| Anthropic | OpenAI | Gemini | Bedrock | |
|---|---|---|---|---|
| 방식 | 명시적 cache_control |
자동(implicit), GPT-5.6+부터 명시 모드 추가 | implicit 기본 + explicit(캐시 객체) | 명시적 cachePoint |
| 최소 토큰 | 512~4,096 (모델별) | 1,024 | 2,048~4,096 | 1,024~4,096 (3섹션 합산) |
| TTL | 5분 기본, 1시간 옵션, 히트 시 리셋 | 30분 고정(5.6+) | explicit 기본 1시간, 임의 지정 | 5분 기본, 일부 모델 1시간 |
| 쓰기 비용 | 1.25배(5분) / 2배(1시간) | 1.25배(5.6+, 이전 무료) | 없음, 대신 스토리지 시간당 과금 | 모델별 상이 |
| 읽기 할인 | 90% | 90% | 90% (implicit은 절감 보장 없음) | 모델별 “할인 요율” |
구조가 셋으로 갈린다. Anthropic·Bedrock은 쓸 때 웃돈을 내고 읽을 때 90% 할인받는 명시적 모델이고, OpenAI는 자동으로 시작해 명시 제어로 수렴하는 중이고, Gemini의 explicit만 쓰기 웃돈 대신 시간당 보관료를 받는 다른 축이다. 어느 쪽이든 “정적인 것을 앞에”라는 채점 기준은 동일하다. 하나 더 — Bedrock에서 Prompt Caching은 온디맨드 전용이라 배치 추론 API와 병용할 수 없다. 무신사 글의 다음 레버가 Batch API 전환인데, 두 레버는 요청 단위에서는 양자택일이라는 점은 계산에 넣어야 한다.
잘 설계된 프롬프트는 싸다
무신사 글의 표면 서사는 비용 절감이지만 한 꺼풀 아래 서사는 이것이다. 히트율 98%는 캐싱 기능의 성능이 아니라 그 팀 프롬프트 구조의 점수다. 고정과 변동이 깨끗이 분리된 프롬프트였기 때문에 98%가 나왔고 예측과 실측이 1%p로 붙었다.
이 성적표의 좋은 점은 채점 기준이 좋은 설계와 정확히 겹친다는 것이다. 변하지 않는 규칙을 앞에 고정하고, 매번 바뀌는 값을 뒤로 보내고, 직렬화를 결정론적으로 만들고, 상태 변경을 프리픽스 수정이 아니라 append로 표현하는 것 — 캐시 히트율을 올리는 이 목록은 그대로 컨텍스트 엔지니어링의 기본기 목록이다. 컨텍스트를 어지럽게 쓰면 비싸지고 규율 있게 쓰면 싸진다. 비용과 설계 품질이 같은 방향을 가리키는 지표는 흔치 않다.
그러니 LLM 비용 그래프가 우상향 중이라면 순서는 이렇다. 히트율부터 계측한다. 낮으면 프롬프트에서 앞쪽에서 변하는 것을 찾는다. 그것을 찾아 뒤로 보내는 일은 대개 코드 몇 줄이고, 효과는 이 글의 사례들이 반복 측정했듯 절반 이상의 청구액이다. 성적표는 이미 매 요청마다 발행되고 있다. 열어보기만 하면 된다.