.cg-article{–accent:#4f46e5;–accent-soft:#eef2ff;–accent-line:#c7d2fe;–navy:#312e81;–warn:#d97706;–warn-soft:#fffbeb;line-height:1.75;color:#1f2937;font-size:17px;word-break:keep-all}
.cg-article h2{margin:2.2em 0 .7em;padding-left:.55em;border-left:5px solid var(–accent);font-size:1.42em;line-height:1.4;color:var(–navy)}
.cg-article h3{margin:1.6em 0 .5em;font-size:1.13em;color:var(–accent)}
.cg-article p{margin:.7em 0}
.cg-lead{background:var(–accent-soft);border:1px solid var(–accent-line);border-radius:12px;padding:18px 20px;margin:1.2em 0;font-size:1.02em}
.cg-lead strong{color:var(–accent)}
.cg-table{width:100%;border-collapse:collapse;margin:1.1em 0;font-size:.97em}
.cg-table th,.cg-table td{border:1px solid #e5e7eb;padding:11px 13px;text-align:left;vertical-align:top}
.cg-table th{background:var(–accent);color:#fff;font-weight:600}
.cg-table tr:nth-child(even) td{background:#f9fafb}
.cg-check{list-style:none;padding:0;margin:1.1em 0}
.cg-check li{position:relative;padding:9px 9px 9px 34px;margin:6px 0;background:var(–accent-soft);border-radius:8px}
.cg-check li::before{content:”✓”;position:absolute;left:12px;top:9px;color:var(–accent);font-weight:700}
.cg-warn{background:var(–warn-soft);border-left:5px solid var(–warn);border-radius:8px;padding:16px 18px;margin:1.3em 0}
.cg-warn strong{color:var(–warn)}
.cg-faq{margin:1.5em 0}
.cg-faq dt{font-weight:600;margin:1.1em 0 .3em;color:var(–accent)}
.cg-faq dd{margin:0 0 .6em}
.cg-article pre{background:#1e1b2e;color:#e9e7ff;border-radius:10px;padding:14px 16px;overflow-x:auto;font-size:.86em;line-height:1.55;margin:1.1em 0}
.cg-article pre code{background:none;color:inherit;padding:0}
.cg-article code{background:var(–accent-soft);color:var(–navy);padding:.1em .4em;border-radius:5px;font-size:.9em}
.cg-note{font-size:.9em;color:#6b7280;border-top:1px dashed #d1d5db;margin-top:2.4em;padding-top:1em}
프롬프트 캐싱은 여러 요청에서 반복되는 앞부분 컨텍스트를 한 번만 처리하고, 이후 요청에서는 그 처리 결과를 재사용하는 기능입니다. 시스템 프롬프트, 문서, 도구 정의처럼 매번 똑같이 들어가는 큰 입력을 캐시에 올려 두면, 다음 요청에서는 같은 부분을 다시 토큰화·연산하지 않습니다. 결과적으로 캐시된 구간의 입력 토큰 비용이 크게 낮아지고, 첫 토큰까지의 지연(TTFT)도 줄어듭니다. 핵심 조건은 단 하나, 요청 앞부분이 바이트 단위로 동일한 프리픽스여야 한다는 것입니다. 그래서 캐시 효과는 기능을 켜는 것만으로 나오지 않고, 프롬프트를 어떻게 구성하느냐에 달려 있습니다.
프롬프트 캐싱이란 무엇인가
LLM은 요청을 받으면 입력 전체를 토큰으로 변환하고, 각 토큰의 내부 표현(키·값 텐서)을 계산합니다. 같은 시스템 프롬프트와 같은 참조 문서를 매 요청마다 보낸다면, 모델은 매번 동일한 연산을 처음부터 반복합니다. 프롬프트 캐싱은 이 반복 연산의 중간 결과를 일정 시간 저장해 두었다가, 다음 요청의 앞부분이 같으면 저장된 상태에서 이어서 처리합니다.
중요한 것은 캐싱 대상이 “텍스트 문자열”이 아니라 “처리된 컨텍스트 상태”라는 점입니다. 따라서 캐시가 적중하면 모델은 그 구간을 다시 읽지도, 다시 연산하지도 않습니다. 비용 청구에서 캐시 적중 구간의 입력 토큰은 일반 입력보다 훨씬 낮은 단가로 계산되고, 응답 품질은 캐시를 쓰지 않을 때와 동일합니다. 캐싱은 모델의 출력을 바꾸는 기능이 아니라, 같은 출력을 더 싸고 빠르게 얻는 최적화입니다.
캐시 히트의 핵심 — 프리픽스 일치
프롬프트 캐싱은 요청의 가장 앞에서부터 한 글자씩 비교해, 이전에 캐시된 내용과 일치하는 구간까지만 재사용합니다. 즉 캐시는 프리픽스(접두) 단위로 동작합니다. 앞에서 단 한 토큰이라도 달라지면, 그 지점 이후로는 캐시가 깨지고 다시 계산됩니다.
이 성질이 설계의 모든 것을 결정합니다. 자주 바뀌는 값을 프롬프트 앞쪽에 두면 캐시는 거의 적중하지 않습니다. 예를 들어 시스템 프롬프트 맨 앞에 현재 시각이나 요청 ID, 사용자 이름을 넣으면 매 요청마다 프리픽스가 달라져 캐시가 무력화됩니다. 반대로 정적인 내용을 앞에, 가변적인 내용을 뒤에 배치하면 긴 공통 프리픽스가 유지돼 캐시 적중률이 올라갑니다.
| 요소 | 변동성 | 권장 배치 |
|---|---|---|
| 시스템 프롬프트·역할 지시 | 거의 고정 | 맨 앞 |
| 도구·함수 정의(스키마) | 거의 고정 | 앞쪽 |
| 참조 문서·RAG 컨텍스트 | 세션 내 고정 | 앞~중간 |
| 대화 누적 이력 | 점진 증가 | 중간 |
| 이번 사용자 질문·동적 변수 | 매번 변함 | 맨 뒤 |
정적 컨텍스트를 앞에 두는 설계
실무에서 가장 흔한 실수는 동적 데이터를 시스템 프롬프트에 섞는 것입니다. 아래는 캐시를 깨는 구성과 살리는 구성의 차이를 단순화한 예시입니다.
# 캐시를 깨는 배치 — 동적 값이 앞에 있음
system = f"현재 시각: {now}n사용자: {user_name}n{긴_지침_5000토큰}"
# 캐시를 살리는 배치 — 정적 지침이 앞, 동적 값은 뒤
system = f"{긴_지침_5000토큰}" # 항상 동일 → 캐시 프리픽스
messages = [
{"role": "user", "content": f"[컨텍스트] 시각:{now} 사용자:{user_name}n질문: {q}"}
]제공사에 따라 캐시 지점을 명시적으로 표시하는 방식도 있습니다. 예를 들어 일부 API는 캐시하고 싶은 블록에 캐시 표시(cache control)를 붙여 “여기까지를 캐시 경계로 삼으라”고 지정합니다. 다른 API는 별도 표시 없이 일정 길이 이상의 프리픽스를 자동으로 캐시합니다. 자동·수동 어느 쪽이든 원리는 같습니다. 공통 프리픽스를 길고 안정적으로 유지하는 것이 곧 비용 절감입니다.
# 캐시 경계를 명시하는 방식(개념 예시)
{
"role": "system",
"content": [
{ "type": "text", "text": "",
"cache_control": { "type": "ephemeral" } } # 이 블록을 캐시
]
}캐시 TTL과 수명
캐시는 영구적이지 않습니다. 마지막으로 사용된 뒤 일정 시간(TTL)이 지나면 만료되어 다음 요청은 다시 전체를 처리합니다. TTL은 제공사·옵션에 따라 수 분 단위인 경우가 일반적이며, 캐시가 재사용될 때마다 수명이 갱신되는 방식이 흔합니다. 일부 제공사는 더 긴 TTL 옵션을 별도로 제공하기도 합니다. 정확한 만료 시간과 갱신 규칙은 사용하는 API의 공식 문서에서 확인해야 합니다.
TTL이 짧다는 점은 트래픽 패턴과 직결됩니다. 요청이 촘촘하게 들어오는 서비스는 캐시가 계속 살아 있어 적중률이 높지만, 요청 간격이 TTL보다 긴 산발적 워크로드는 매번 캐시가 만료된 상태로 들어와 효과를 보기 어렵습니다. 또한 캐시 생성(쓰기) 자체에 약간의 추가 비용이 붙는 모델도 있어, 적중이 거의 없는 일회성 요청에 캐시를 거는 것은 오히려 손해일 수 있습니다.
캐시 쓰기 비용과 적중률을 함께 보세요.
캐시는 “처음 한 번 올릴 때”의 비용과 “재사용할 때”의 절감이 분리돼 있습니다. 같은 프리픽스가 TTL 안에 충분히 자주 재사용되지 않으면, 캐시 쓰기 오버헤드만 내고 절감은 못 챙기는 상황이 생깁니다. 적중률을 측정하지 않고 캐싱을 켜는 것은 위험합니다.
비용·지연 절감의 원리
캐싱이 비용을 낮추는 이유는 단순합니다. 캐시 적중 구간의 입력 토큰은 일반 입력 토큰보다 낮은 단가로 청구됩니다. 정확한 할인 비율은 모델과 제공사마다 다르므로 공식 가격 페이지 확인을 권장하지만, 방향성은 분명합니다. 프롬프트의 큰 부분이 고정 컨텍스트(긴 시스템 프롬프트, 대용량 문서, 많은 도구 정의)이고 그 부분이 반복될수록 절감 폭이 커집니다.
지연 측면에서도 이득이 있습니다. 캐시된 구간은 다시 연산하지 않으므로, 입력이 길수록 첫 토큰까지 걸리는 시간이 눈에 띄게 줄어듭니다. 긴 문서를 매 턴 참조하는 챗봇이나, 동일한 대용량 지침으로 수천 건을 분류하는 파이프라인에서 체감 차이가 큽니다.
| 구분 | 캐싱 미적용 | 캐싱 적중 |
|---|---|---|
| 앞부분 컨텍스트 처리 | 매 요청 전체 재연산 | 저장 상태 재사용 |
| 입력 토큰 단가(캐시 구간) | 일반 단가 | 대폭 인하(문서 확인) |
| 첫 토큰 지연(TTFT) | 입력 길이에 비례 | 캐시 구간만큼 단축 |
| 출력 품질 | 동일 | 동일 |
적합한 경우와 부적합한 경우
프롬프트 캐싱이 잘 맞는 작업은 “큰 고정 입력 + 작은 가변 입력”의 형태이고, 같은 고정 입력이 짧은 간격으로 반복되는 경우입니다.
- 긴 시스템 프롬프트나 장문 가이드라인을 모든 요청에 공통으로 싣는 에이전트·챗봇
- 같은 문서·코드베이스를 두고 여러 질문을 던지는 문서 QA, 코드 분석
- 동일한 분류 지침과 예시(few-shot)로 대량 항목을 처리하는 배치성 작업
- 도구 정의가 많은 함수 호출 에이전트(스키마 자체가 큰 고정 프리픽스)
반대로 캐싱이 도움이 안 되거나 손해인 경우도 분명합니다.
- 요청마다 프롬프트가 통째로 달라져 공통 프리픽스가 거의 없는 작업
- 고정 컨텍스트가 짧아 캐시 최소 길이 기준에 못 미치는 경우
- 요청 간격이 TTL보다 길어 매번 캐시가 만료되는 산발적 트래픽
- 동적 값(시각·ID·사용자명)을 프롬프트 앞에 둬 프리픽스가 매번 깨지는 구조
효과를 측정하는 법
캐싱은 켜고 끝내는 기능이 아니라, 적중률을 보며 프롬프트 구조를 다듬는 작업입니다. 대부분의 API는 응답의 사용량(usage) 정보에 캐시 관련 토큰 수를 분리해 돌려줍니다. 캐시 생성 토큰(처음 올린 양)과 캐시 읽기 토큰(재사용된 양)을 구분해 기록하면 적중률을 계산할 수 있습니다.
# 응답 usage에서 캐시 지표 확인(개념 예시)
usage = response.usage
created = usage.cache_creation_input_tokens # 캐시에 새로 쓴 양
read = usage.cache_read_input_tokens # 캐시에서 재사용한 양
fresh = usage.input_tokens # 캐시 못 탄 입력
hit_rate = read / (read + fresh + created)
# hit_rate가 낮으면 → 프리픽스가 자주 깨지는지, 동적 값 위치를 점검적중률이 기대보다 낮다면 거의 항상 프리픽스가 원인입니다. 동적 변수가 앞에 끼어 있는지, 비결정적 직렬화(딕셔너리 키 순서, JSON 공백 차이)로 같은 데이터가 다른 바이트로 나가는지, 도구 정의 순서가 매번 달라지는지를 점검합니다. 직렬화는 키 정렬을 고정하는 것만으로도 적중률이 크게 개선되는 경우가 많습니다.
멀티턴 대화에서의 캐싱
대화형 서비스는 프롬프트 캐싱과 특히 잘 맞습니다. 한 대화 안에서 시스템 프롬프트와 그동안 쌓인 대화 이력은 고정된 채 맨 뒤에 새 사용자 발화만 덧붙기 때문입니다. 즉 턴이 진행될수록 공통 프리픽스가 점점 길어지고, 그 긴 프리픽스가 다음 턴에서 그대로 재사용됩니다. 대화가 길어질수록 매 턴 다시 처리해야 할 입력이 누적되는데, 캐싱이 없으면 이 누적분을 매번 전부 재연산해 비용과 지연이 턴마다 불어납니다. 캐싱을 켜면 이미 처리한 앞부분은 건너뛰므로, 긴 대화일수록 절감 효과가 커집니다.
다만 대화 이력을 압축·요약해 중간을 갈아끼우는 설계를 함께 쓴다면 주의가 필요합니다. 이력의 앞부분을 요약문으로 치환하는 순간 그 지점부터 프리픽스가 바뀌어 캐시가 깨지기 때문입니다. 요약은 캐시 절감과 컨텍스트 길이 관리라는 두 목표가 충돌하는 지점이므로, 요약 주기를 너무 잦지 않게 잡아 캐시가 충분히 재사용된 뒤에 갱신되도록 설계하는 편이 균형이 좋습니다.
자주 묻는 질문
- 프롬프트 캐싱을 켜면 응답 내용이 달라지나요?
- 아닙니다. 캐싱은 동일한 출력을 더 적은 연산으로 얻는 최적화입니다. 모델이 보는 입력은 같으므로 결과 품질에는 영향을 주지 않습니다. 비용과 첫 토큰 지연만 줄어듭니다.
- 캐시는 얼마나 오래 유지되나요?
- 마지막 사용 후 일정 시간(TTL)이 지나면 만료됩니다. TTL과 갱신 규칙은 제공사·옵션마다 다르고 비교적 짧은 편이라, 정확한 값은 사용하는 API의 공식 문서에서 확인해야 합니다. 재사용될 때마다 수명이 연장되는 방식이 일반적입니다.
- 비용은 얼마나 줄어드나요?
- 캐시 적중 구간의 입력 토큰이 일반 단가보다 낮게 청구돼 절감됩니다. 정확한 할인율은 모델·제공사마다 달라 공식 가격 페이지 확인을 권장합니다. 고정 컨텍스트 비중이 크고 반복이 잦을수록 절감 폭이 커집니다.
- 왜 동적 값을 뒤에 두라고 하나요?
- 캐시는 요청 앞에서부터 일치하는 프리픽스만 재사용합니다. 앞쪽에 매번 바뀌는 값이 있으면 그 지점부터 캐시가 깨져 효과가 사라집니다. 정적 지침을 앞에, 가변 데이터를 뒤에 두면 공통 프리픽스가 길게 유지됩니다.
- 적중률이 낮을 때 가장 먼저 볼 곳은?
- 프롬프트 직렬화입니다. JSON 키 순서나 공백, 도구 정의 순서가 요청마다 달라지면 내용이 같아도 바이트가 달라 캐시가 깨집니다. 키 정렬과 직렬화 형식을 결정적으로 고정하는 것이 첫 점검 항목입니다.
이 글은 Anthropic·OpenAI·Google 등의 공개 API 문서에 설명된 프롬프트(컨텍스트) 캐싱의 일반 동작 원리를 정리한 것입니다. 최소 캐시 길이, TTL, 단가 인하 비율 등 구체적 수치는 모델과 제공사, 시점에 따라 다르므로 실제 적용 전에는 각 API의 공식 문서와 가격 페이지를 확인하시기 바랍니다.