
지난 글에서 같은 prefix를 60번 호출해 캐싱의 비용 곡선을 그렸다.
그 글 마지막에 이렇게 약속했다.
“다음 글에서는 같은 캐싱 설계를 실제 워크로드에 옮겨본다.
이번 글이 ‘캐싱은 왜 켜는가’였다면
다음 글은 ‘캐싱을 어떻게 붙이는가’다.”
이번 글이 그 약속이다.
먼저 솔직하게 밝힌다.
운영 코드를 그대로 옮길 수는 없어서 핀테크에서 흔한 거래 메시지 분류기를 대표 시나리오로 재구성했다.
수치는 앞 글의 실측을 외삽한 것이고 설계 판단은 실제로 캐시를 붙이며 부딪힌 것들이다.
“왜 켜는가”는 앞의 세 글에서 끝냈으니 여기서는 마커를 어디에 박고
TTL을 어떤 코드 경로에서 가를지를 일지처럼 쓴다.
워크로드부터 정의한다
캐싱 설계는 워크로드 정의에서 시작한다.
여기서 잡은 시나리오는 이렇다.
거래 메시지 한 건을 받아 사전 정의된 라벨로 분류하는 작업이다.
시스템 프롬프트는 세 덩어리로 구성된다.
- 분류 라벨 정의: 약 800 토큰. 거의 안 바뀐다.
- few-shot 예시 20개: 약 2,400 토큰. 분기마다 손보지만 평소엔 고정이다.
- 회사 약관·규정 발췌: 약 1,600 토큰. 규정 개정 때만 바뀐다.
여기에 거래 메시지 한 건(약 50 토큰)이 사용자 입력으로 들어온다.
합치면 호출당 입력이 5천 토큰에 육박하는데 그중 4,800 토큰이 매 호출 똑같다.
캐싱 교과서에 나오는 가장 이상적인 모양이다.
길고, 변하지 않고, 매 호출 재사용되는 prefix.

prefix를 캐시 친화적으로 배치한다
캐시가 먹으려면 prefix가 정확히 일치해야 한다.
그래서 메시지 레이아웃을 “안 변하는 것 → 가끔 변하는 것 → 매번 변하는 것” 순으로 고정한다.
[1] 시스템 프롬프트 (라벨 정의) ← 거의 불변
[2] 도구 정의 ← 거의 불변
[3] 약관·규정 발췌 ← 규정 개정 시에만
[4] few-shot 예시 20개 ← 분기마다
---- 여기까지 캐시 ----
[5] 거래 메시지 한 건 ← 매 호출 변함
원칙은 단순하다.
자주 바뀌는 걸 뒤로 보낼수록 앞쪽 캐시가 오래 산다.
여기서 핀테크 특유의 함정이 하나 있다.
거래 시각, 계좌번호, 요청 ID 같은 동적 값을 무심코 시스템 프롬프트 앞쪽에 끼워넣기 쉽다.
“오늘 날짜: 2026-06-03” 한 줄이 prefix에 들어가는 순간 자정마다 캐시가 통째로 깨진다.
동적 값은 반드시 [5]번, 사용자 입력 블록으로 내려야 한다.
여담이지만, 이 함정은 분류기가 아니라 챗봇을 만들며 실제로 밟았다.
시스템 프롬프트 맨 앞에 “현재 시각” 한 줄을 넣어두고 멀티턴을 돌렸더니 턴마다 cache_read가 0으로 찍혔다.
prefix 길이만 한참 의심하다 매 턴 바뀌는 게 그 시각 한 줄뿐이라는 걸 뒤늦게 봤다.
그 줄을 사용자 입력 쪽으로 내리자 두 번째 턴부터 캐시가 붙기 시작했다.
캐싱은 켜는 일보다 무엇이 prefix를 더럽히는지 찾아내는 게 일의 대부분이었다.
cache_control 마커를 어디에 박는가
앞 글에서 캐시 포인트는 최대 4개까지 둘 수 있다고 했다.
이 워크로드는 그 4개를 알뜰하게 다 쓰기 좋은 구조다.
system = [
{"type": "text", "text": LABEL_DEFINITIONS,
"cache_control": {"type": "ephemeral"}}, # 캐시 포인트 1
]
tools = [...] # 도구 정의에도 cache_control 부착 가능
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": POLICY_EXCERPT,
"cache_control": {"type": "ephemeral"}}, # 캐시 포인트 2
{"type": "text", "text": FEW_SHOT_BLOCK,
"cache_control": {"type": "ephemeral"}}, # 캐시 포인트 3
{"type": "text", "text": transaction_msg}, # 매번 변하는 입력 (마커 없음)
],
}
]
포인트를 단계별로 쪼개두면 일부만 바뀌어도 앞쪽은 살아남는다.
few-shot을 한 개 추가하면 캐시 포인트 3 이후만 다시 써지고
라벨 정의와 약관 캐시(포인트 1·2)는 그대로 재사용된다.
마커를 맨 끝 한 곳에만 몰아 박으면 이 부분적 생존이 안 된다.
few-shot 한 줄 고친 게 전체 캐시를 날리는 일이 생긴다.

TTL을 코드 경로에서 가른다
앞 글의 실측이 남긴 결론은 한 줄이었다.
“TTL 선택은 비용이 아니라 호출 간격으로 결정해야 한다.”
그래서 TTL을 상수로 박지 않고 워크플로우 성격에 따라 함수로 고른다.
def pick_ttl(workflow: str) -> str:
# 실시간 분류: 거래가 초 단위로 들어와 호출 간격이 5분 안쪽
if workflow == "realtime_classify":
return "5m"
# 야간 배치·정산: 호출이 띄엄띄엄, 간격이 5분을 자주 넘김
if workflow == "nightly_batch":
return "1h"
return "5m"
실시간 분류는 거래가 쉴 새 없이 들어와 캐시가 5분 안에 계속 갱신된다.
쓰기 단가가 싼 5분 TTL이 맞다.
반대로 야간 배치는 호출이 드물어 5분 캐시가 식어버린다.
캐시 미스로 매번 처음부터 다시 쓰느니 2배 쓰기 비용을 한 번 무는 1시간 TTL이 싸다.
앞 글에서 본 5분 캐시에 1시간 호출이 free-ride하던 함정도 여기서 조심해야 한다.
두 워크플로우가 같은 prefix를 공유하면 TTL이 섞인다.
그래서 실시간과 배치는 prefix 맨 앞에 워크플로우 태그를 한 줄 달아 캐시 키를 분리했다.
캐시 미스를 로깅한다
캐싱은 켜두는 게 아니라 보고 있는 거다.
매 호출의 usage 네 가지를 구조화해 남긴다.
u = response.usage
log.info("cache", extra={
"workflow": workflow,
"input": u.input_tokens,
"cwrite": u.cache_creation_input_tokens,
"cread": u.cache_read_input_tokens,
"output": u.output_tokens,
})
이 로그 하나로 운영 중 이상을 바로 잡아낸다.
cread가 계속 0이다 → prefix가 매 호출 깨지고 있다. 동적 값이 앞쪽에 샜는지 본다.cwrite가 매 호출 크게 잡힌다 → TTL이 짧거나 호출 간격이 길어 캐시가 자꾸 새로 써진다.- 실시간 워크플로우에서
cwrite에 작은 값(20~40)이 꾸준히 붙는다
→ 앞 글에서 본 5분 TTL의 user message 확장 비용이다. 정상이다.
마지막 항목을 모르면 “왜 cache_creation이 0이 아니지?”에서 반나절을 태운다.
앞 글에서 실측으로 한 번 짚었던 패턴이 운영 알람의 임계값을 잡는 데 그대로 쓰인다.

가정한 절감액과 그 한계
앞 글의 호출당 수치를 이 워크로드에 그대로 얹으면 그림은 분명하다.
하루 1만 건, 실시간 5분 TTL 기준으로 캐싱 OFF 대비 월 1만 달러대 절감이 나온다.
다만 이건 가정이다.
실제 운영에서는 세 가지가 그림을 흐린다.
- 캐시 미스율: few-shot이나 약관을 손대는 빈도가 높으면 절감이 줄어든다.
- 호출 간격 분포: 거래가 한산한 시간대엔 5분 캐시가 식어 미스가 늘어난다.
- prefix 오염: 동적 값이 한 군데라도 새면 절감이 통째로 사라진다.
그래서 가정 숫자는 “상한선”으로만 본다.
진짜 숫자는 위 로그를 한 주 쌓아 미스율을 곱해봐야 나온다.
캐싱 4부작을 닫으며
세 글에 걸쳐 캐싱을 한 바퀴 돌았다.
- 개념 — 캐싱은 KV 행렬을 재사용하는 표시다. 쓰기는 비싸고 읽기는 거의 공짜.
- 실측 — 60번 호출로 손익분기(2~3회)와 16% 비용을 눈으로 확인했다.
- 설계 — prefix를 캐시 친화적으로 배치하고, 마커를 단계별로 박고, TTL을 호출 간격으로 가르고, 미스를 로깅한다.
캐싱은 단가를 안 바꾼다.
같은 토큰을 비싼 길로 보낼지 싼 길로 보낼지, 그 흐름만 바꾼다.
그 흐름을 설계하는 감각이 결국 LLM 비용을 다루는 일의 핵심이었다.
함께 읽으면 좋은 글
- 프롬프트 캐싱, 처음 도입할 때 알아둬야 하는 7가지
- 프롬프트 캐싱, 진짜 손익분기는 어디인가 – Opus 4.7로 60번 호출해서 측정한 비용 곡선
- 못 미더운 외부 소스를 가두는 법 2가지 – 서킷브레이커와 소스 health 관측
글쓴이 · Plutojoshua
핀테크에서 LLM과 AI 에이전트를 실제 서비스에 적용하는 개발자다. 카카오 AI 앰배서더로 Kanana 모델을 앱에 직접 연동해 검증하고, 비전공자로 시작해 실제 코드와 측정값을 근거로 “왜 그렇게 설계했는지”를 1인칭으로 기록한다. 운영자 소개는 About, 코드는 GitHub에서 볼 수 있다.
“프롬프트 캐싱을 실제 워크로드에 붙이기 – 핀테크 거래 분류기 설계 일지”에 대한 2개의 생각