같은 지시와 자료를 반복해서 보내는 시스템에서 재사용 전략은 비용과 지연시간을 좌우하는 아키텍처 결정입니다. 이 수업에서는 프롬프트 캐싱의 유일한 불변식, 캐시를 조용히 깨뜨리는 요인들, 모듈형 프롬프트와 스킬의 점진적 공개, 그리고 재사용이 실제로 이익인지 판단하는 손익 계산을 다룹니다.
캐싱의 불변식은 하나뿐이다
프롬프트 캐싱에 대해 외울 것은 사실상 하나입니다.
캐싱은 접두어 일치다. 접두어 어디든 한 바이트가 바뀌면 그 뒤는 전부 무효가 된다.
나머지 규칙은 전부 여기서 따라 나옵니다. 렌더링 순서는 tools → system → messages이므로, 툴 정의는 가장 앞이고 대화는 가장 뒤입니다. 툴을 바꾸면 전부 무효, 시스템 프롬프트를 바꾸면 시스템과 대화가 무효, 마지막 대화 턴을 덧붙이는 것은 앞쪽에 영향을 주지 않습니다.
이 순서를 이해하면 배치 결정이 기계적으로 나옵니다.
system = [
{"type": "text", "text": BASE_POLICY},
{"type": "text", "text": DOMAIN_KNOWLEDGE},
{"type": "text", "text": FEW_SHOT_EXAMPLES,
"cache_control": {"type": "ephemeral"}}, # 여기까지 캐시
]
messages = [
*history,
{"role": "user", "content": today_question}, # 매번 달라지는 부분은 뒤에
]
캐시 지점은 요청당 최대 네 개까지 둘 수 있습니다. 여러 개를 두는 이유는 안정성 경계마다 하나씩 두어, 어느 한 계층이 바뀌어도 그보다 앞선 계층은 살아남게 하기 위해서입니다.
멀티턴 대화에서는 가장 최근 턴의 마지막 블록에 지점을 두는 방식이 표준입니다. 이렇게 하면 다음 요청이 이전 대화 전체를 접두어로 재사용합니다.
유지 시간은 기본이 짧고, 더 긴 유지 시간을 선택할 수도 있습니다. 다만 유지 시간이 길수록 쓰기 비용이 비싸집니다. 트래픽이 촘촘하면 기본값으로 충분하고, 요청 간격이 길어 캐시가 자꾸 만료되는 경우에만 긴 쪽을 검토합니다.
캐시를 조용히 깨뜨리는 것들
캐시가 안 걸려도 오류는 나지 않습니다. 그래서 문제를 발견하는 유일한 방법은 응답의 사용량을 확인하는 것입니다.
u = response.usage
print(u.cache_creation_input_tokens) # 이번에 캐시에 쓴 양
print(u.cache_read_input_tokens) # 캐시에서 읽은 양
print(u.input_tokens) # 캐시되지 않은 나머지
반복 요청인데 cache_read_input_tokens가 계속 0이라면 접두어가 매번 달라지고 있다는 뜻입니다. 흔한 원인은 다음과 같습니다.
| 원인 | 왜 문제인가 |
|---|---|
| 시스템 프롬프트에 현재 시각을 넣음 | 매 요청 접두어가 달라짐 |
| 요청 ID나 세션 ID를 앞쪽에 삽입 | 같은 이유 |
| 딕셔너리를 정렬 없이 직렬화 | 실행마다 키 순서가 달라질 수 있음 |
| 사용자별로 툴 목록을 다르게 구성 | 툴은 맨 앞이라 사용자 간 공유가 불가능 |
| 조건에 따라 시스템 프롬프트 문단을 붙였다 뗐다 함 | 조합의 수만큼 서로 다른 접두어가 생김 |
여기에 한 가지 덜 알려진 원인이 있습니다. 접두어가 캐시 최소 길이에 못 미치는 경우입니다. 최소 길이는 모델마다 다르며, 그보다 짧으면 cache_control을 붙여도 조용히 캐시되지 않습니다. 무효화 요인을 아무리 찾아도 안 나온다면 이 가능성을 확인해야 합니다.
동시 요청도 함정입니다. 같은 접두어로 요청 N개를 동시에 보내면 아무도 캐시를 읽지 못합니다. 첫 요청이 아직 쓰는 중이기 때문입니다. 대량 병렬 처리에서는 한 건을 먼저 보내 응답이 시작되는 것을 확인한 뒤 나머지를 푸는 방식이 낫습니다.
마지막으로 모델을 바꾸면 캐시는 무효입니다. 캐시는 모델 단위로 유지됩니다. 세션 도중 모델을 갈아타는 구조는 캐시 관점에서 비쌉니다. 저렴한 모델을 쓰고 싶다면 메인 루프의 모델을 바꾸기보다 서브에이전트를 별도 모델로 띄우는 편이 낫습니다.
모듈형 프롬프트와 스킬의 점진적 공개
재사용의 두 번째 축은 필요할 때만 불러오는 구조입니다.
프롬프트를 모듈로 쪼개는 목적은 두 가지입니다. 하나는 앞에서 본 캐시 경계를 만드는 것이고, 다른 하나는 모든 지식을 항상 컨텍스트에 두지 않는 것입니다. 팀이 쌓아온 절차와 규칙을 전부 시스템 프롬프트에 넣으면, 그 요청과 무관한 내용까지 매번 비용을 냅니다.
스킬(Skills) 은 이 문제에 대한 구조적 답입니다. 스킬은 지시와 참고 파일을 담은 폴더이며, 기본적으로 컨텍스트에 들어가는 것은 설명 한 줄뿐입니다. 모델은 작업과 관련 있다고 판단할 때 본문을 읽어 옵니다. 이 방식을 점진적 공개(progressive disclosure)라고 합니다.
skills/
invoice-review/
SKILL.md # 설명과 절차 (필요할 때 읽힘)
checklist.md # 상세 점검 항목
templates/
report.md
스킬로 옮기기 좋은 것과 프롬프트에 남겨야 하는 것의 구분은 다음과 같습니다.
- 스킬로: 특정 작업에서만 필요한 절차, 참고 표, 템플릿, 예외 처리 규칙
- 프롬프트에: 모든 요청에 적용되는 원칙, 금지 사항, 출력 규약
스킬을 쓸 때 주의할 점이 있습니다. 설명 줄은 항상 컨텍스트에 들어갑니다. 스킬이 늘어날수록 설명 줄의 총량도 늘어나므로, 설명은 트리거 조건이 드러나게 짧고 명확하게 씁니다. 유사한 스킬을 여러 개로 쪼개기보다 하나로 합치는 편이 나은 경우가 많습니다.
툴이 많은 시스템에서는 같은 원리가 툴에도 적용됩니다. 툴 스키마를 전부 항상 싣는 대신 필요한 것만 발견해 불러오는 방식이 있으며, 이때 툴 정의가 교체되는 것이 아니라 덧붙는 방식이라 앞쪽 접두어가 살아남습니다. 툴 목록을 통째로 갈아 끼우는 것과 이 방식의 캐시 영향이 다르다는 점이 시험에서 묻는 지점입니다.
재사용이 언제 이익인지 계산하기
캐싱은 공짜가 아닙니다. 캐시에 쓰는 요청은 일반 입력보다 비싸고, 읽는 요청은 훨씬 쌉니다. 그래서 손익분기가 존재합니다.
계산의 구조는 단순합니다. 캐시 없이 N번 보내면 비용은 N배입니다. 캐시를 쓰면 첫 요청에서 쓰기 할증을 내고, 나머지 N-1번은 아주 낮은 비율만 냅니다. 기본 유지 시간에서는 두 번만 재사용해도 이익이고, 유지 시간을 늘린 경우에는 쓰기 할증이 더 크므로 세 번 이상 재사용해야 남습니다.
여기서 실무적으로 중요한 것은 절대 횟수가 아니라 유지 시간 안에 재사용이 일어나는가입니다. 하루에 100번 호출되더라도 요청 간격이 유지 시간보다 길면 매번 쓰기만 하고 읽지 못해 오히려 손해입니다.
그래서 판단 순서는 이렇습니다.
- 접두어가 실제로 공유되는가? 요청마다 앞부분이 다르면 캐싱 자체가 성립하지 않습니다.
- 재사용 간격이 유지 시간 안에 들어오는가? 트래픽 분포를 봐야 알 수 있습니다.
- 접두어가 최소 길이를 넘는가? 짧으면 조용히 캐시되지 않습니다.
세 조건이 모두 충족되지 않으면 cache_control을 붙이지 않는 편이 낫습니다. 쓰기 할증만 내고 이득이 없기 때문입니다.
트래픽이 끊겼다 몰리는 패턴이라면 미리 데워두는 방법도 있습니다. 서비스 시작 시점이나 트래픽이 몰리기 직전에 접두어만 담은 요청을 한 번 보내 캐시를 만들어 두는 방식입니다. 다만 이것도 쓰기 비용을 앞당겨 내는 것이므로, 첫 요청의 지연시간이 사용자에게 실제로 보이는 경우에만 값어치를 합니다. 배치 작업처럼 첫 응답 시간이 중요하지 않은 곳에서는 불필요합니다.
정리하면 재사용 전략의 결정 순서는 접두어를 공유 가능하게 설계 → 캐시 지점 배치 → 사용량으로 검증 → 트래픽 패턴에 맞춰 유지 시간 조정입니다. 첫 단계를 건너뛰고 캐시 지점만 붙이는 것이 가장 흔한 실패입니다.
시험 함정
- 캐시 지점을 붙이기만 하면 캐싱이 된다고 보는 선택지 — 접두어가 공유되지 않으면 쓰기 비용만 발생합니다.
- 요청 간격을 고려하지 않고 호출 횟수만으로 이익을 판단하는 서술 — 유지 시간 안에 재사용돼야 이득입니다.
- 긴 유지 시간이 언제나 유리하다는 선택지 — 쓰기 할증이 커져 손익분기 재사용 횟수가 올라갑니다.
- 세션 도중 모델을 바꾸면서 캐시가 유지된다고 보는 구성 — 캐시는 모델 단위입니다.
- 동일 접두어 요청을 한꺼번에 병렬 전송하는 설계 — 첫 요청이 쓰는 중이라 아무도 읽지 못합니다.
- 스킬을 늘려도 컨텍스트 비용이 늘지 않는다는 서술 — 각 스킬의 설명 줄은 항상 컨텍스트에 실립니다.
실습 시나리오
실제 시험과 같은 형식의 시나리오 문제입니다.
사내 문서 질의응답 서비스에서 8천 토큰 규모의 공통 지침과 예시를 매 요청에 보내고 있습니다. 캐시 지점을 시스템 프롬프트 끝에 두었는데도 사용량을 보면 캐시 읽기가 잡히는 요청이 절반뿐입니다. 요청은 업무 시간에 몰리고 야간에는 거의 없습니다. 가장 적절한 진단과 조치는 무엇입니까?
빌드 연습 · 공통 지침을 재사용 가능한 구조로 재설계하기
약 45분1.접두어 공유 여부 확인
최근 요청 20건의 프롬프트를 덤프해 앞부분이 실제로 동일한지 바이트 단위로 비교합니다.
기대 결과 · 동일하지 않다면 어느 지점부터 갈라지는지 정확한 위치가 특정됩니다.
2.무효화 요인 제거
발견된 동적 값을 메시지 쪽으로 옮기고 직렬화 순서를 고정합니다.
기대 결과 · 20건의 접두어가 완전히 일치하게 됩니다.
3.캐시 지점 배치
안정성 경계마다 지점을 두되 네 개를 넘지 않게 배치합니다.
기대 결과 · 계층 하나가 바뀌어도 그보다 앞선 계층은 캐시가 유지되는 것이 확인됩니다.
4.적중률과 손익 계산
시간대별 캐시 읽기 비율을 측정하고, 유지 시간을 늘렸을 때의 비용 변화를 추정합니다.
기대 결과 · 유지 시간 변경이 이익인지 손해인지 수치로 판단됩니다.
5.스킬로 분리
특정 작업에서만 쓰이는 절차 하나를 골라 스킬로 옮기고 남은 시스템 프롬프트 길이를 비교합니다.
기대 결과 · 공통 프롬프트가 짧아지고, 해당 작업에서만 스킬 본문이 읽히는 것이 확인됩니다.