본문 바로가기
카테고리 없음

프롬프트 캐싱 (캐시 프리워밍, TTL 변경, 비용절감)

by BOOST YOUR INFORMATION 2026. 5. 30.

프롬프트 캐싱 (캐시 프리워밍, TTL 변경, 비용절감) 참조 이미지
프롬프트 캐싱 - 캐시 프리워밍, TTL 변경, 비용절감

 

 

솔직히 저는 처음에 프롬프트 캐싱을 그냥 "비용 조금 아끼는 기능" 정도로 생각했습니다. 그런데 RAG 시스템에 직접 붙여보고 나서 생각이 완전히 바뀌었습니다. 하루 120달러짜리 API 비용이 13달러로 줄어드는 걸 직접 보고서야, 이게 단순한 최적화 옵션이 아니라 서비스 운영 방식 자체를 바꾸는 기능이라는 걸 실감했습니다.

캐시 프리워밍, 알고 쓰는 것과 모르고 쓰는 것의 차이

일반적으로 프롬프트 캐싱은 cache_control 선언만 붙이면 알아서 돌아간다고 알려져 있지만, 제 경험상 이건 절반짜리 정답입니다. 캐싱을 선언하는 것과 캐싱을 제대로 활용하는 것은 전혀 다른 문제입니다.

제가 운영하던 RAG 시스템은 50,000 토큰 규모의 지식 베이스를 시스템 프롬프트에 통째로 넣고, 사용자 질문에 답하는 구조였습니다. 여기서 RAG(Retrieval-Augmented Generation)란 미리 구축해 둔 지식 데이터베이스에서 관련 내용을 꺼내 LLM의 답변 품질을 높이는 방식을 말합니다. 하루 약 800회 질의가 들어오는 서비스였는데, 캐싱 전에는 매일 4,000만 토큰을 처리하고 있었습니다.

캐싱을 붙이고 나서 처음에 제가 놓친 것이 바로 캐시 프리워밍(pre-warming)이었습니다. 캐시 프리워밍이란 실제 사용자 요청이 들어오기 전에 미리 캐시를 생성해 두는 작업을 말합니다. 이 과정을 생략했더니 서비스가 막 뜨는 시간대에 첫 번째 사용자들이 캐시 미스(cache miss)로 인한 긴 지연을 그대로 경험하게 되었습니다. 캐시 미스란 캐시에 저장된 데이터가 없어 처음부터 다시 연산해야 하는 상황입니다. 100K 토큰 수준의 긴 프롬프트를 캐싱하면 응답 지연이 11.5초에서 2.4초 수준까지 줄어든다는 발표가 있는데(출처: Anthropic 공식 문서), 반대로 말하면 캐시가 없으면 그 11.5초를 첫 사용자가 고스란히 부담한다는 뜻입니다.

저는 이 문제를 해결하기 위해 TTL(Time-To-Live, 캐시 유효 시간) 만료 55분 시점마다 백그라운드에서 자동으로 캐시를 갱신하는 태스크를 구성했습니다. 덕분에 TTL 만료 직전의 캐시 미스도 막을 수 있었고, 실제 히트율은 안정적으로 85% 이상을 유지했습니다.

캐싱 전후 비용 구조를 정리하면 다음과 같습니다.

  • 캐싱 전: 800회 × 50,000 토큰 = 하루 4,000만 토큰 처리, 약 120달러/일
  • 캐싱 후: 캐시 쓰기 1회 + 캐시 읽기 799회, 약 13달러/일
  • 비용 절감율: 약 89%, 월간 약 3,200달러 절감

TTL 변경 사건, API 기본값을 믿으면 안 되는 이유

이건 제가 직접 당한 게 아니었지만, 꽤 충격적인 사건이었습니다. 2026년 3월 6일, Anthropic이 프롬프트 캐시의 기본 TTL을 1시간에서 5분으로 조용히 변경했습니다. 공지 없이 이루어진 변경이었고, 명시적 TTL 설정 없이 cache_control만 선언해 두었던 애플리케이션들은 그날 이후 캐시 히트율이 갑자기 0%에 가깝게 떨어졌습니다(출처: DEV Community).

TTL이란 캐시에 저장된 데이터가 얼마나 오래 유효한지를 결정하는 시간 설정값입니다. 1시간짜리 TTL이 5분으로 줄었다는 건, 1시간마다 한 번 갱신되던 캐시가 5분마다 갱신된다는 뜻입니다. 호출 빈도가 낮은 서비스에서는 이 변경만으로 비용 최적화 효과가 거의 사라지게 됩니다.

현재 기준으로 5분 TTL은 cache_control: { type: 'ephemeral' }로 선언하고, 1시간 TTL을 원한다면 cache_control: { type: 'ephemeral', ttl: '1h' }처럼 명시해야 합니다. 저는 이 사건 이후로 모든 API 설정에서 기본값에 의존하는 부분을 전수 점검하는 습관이 생겼습니다. API 기본값은 언제든 바뀔 수 있고, 그 변경이 반드시 사전 공지되지는 않습니다. API 안정성과 하위 호환성 관점에서 이런 방식의 변경은 분명히 아쉬운 부분입니다. 최소 30일 전 공지와 마이그레이션 가이드 제공이 표준 관행이어야 한다고 생각합니다.

캐시 브레이크포인트(cache breakpoint)도 주의할 지점입니다. 캐시 브레이크포인트란 어디까지를 캐시 단위로 묶을지 지정하는 마커인데, 요청당 최대 4개까지 설정할 수 있습니다. 더 긴 TTL의 항목이 더 앞에 위치해야 하고, 최소 캐시 가능 길이는 대부분 모델에서 1,024 토큰입니다(Haiku는 2,048 토큰). 이보다 짧은 콘텐츠는 cache_control 선언이 있어도 캐시되지 않습니다.

비용절감, 캐시 히트율 관리 없이는 숫자가 보장되지 않는다

89% 비용 절감이라는 수치는 실제로 가능합니다. 그런데 이 수치를 유지하려면 캐시 히트율(cache hit rate)을 꾸준히 모니터링해야 합니다. 캐시 히트율이란 전체 요청 중 캐시에서 응답이 처리된 비율을 말하며, 이 수치가 낮아지면 비용 절감 효과도 그만큼 줄어듭니다.

Anthropic API 응답의 usage 필드에는 cache_read_input_tokens와 cache_creation_input_tokens 값이 포함되어 있습니다. 저는 이 두 값을 추적해서 히트율을 계산했고, 70% 아래로 떨어지면 알림이 오도록 설정했습니다. 실제로 한 번은 배포 직후 히트율이 갑자기 0%로 곤두박질쳤는데, 확인해 보니 시스템 프롬프트 끝에 공백 문자 하나가 끼어들어 캐시 키가 달라진 것이었습니다. 완전 일치(exact match) 방식으로 캐시 키를 비교하기 때문에 생기는 문제입니다. 토큰 하나, 공백 하나만 달라져도 캐시 미스가 발생합니다.

datetime.now() 함정도 직접 겪어본 뒤로는 절대 잊지 않는 교훈입니다. 시스템 프롬프트에 현재 날짜를 동적으로 삽입했더니 캐시 히트율이 0%가 되었습니다. 매 요청마다 시간 값이 달라지니 캐시 키가 매번 새로 생성될 수밖에 없었습니다. 해결 방법은 생각보다 단순했습니다. 날짜 정보를 시스템 프롬프트에서 빼고 유저 메시지에 'Context: 현재 날짜는 {date}입니다. 질문: ...' 형식으로 붙이는 방식으로 분리했습니다. 시스템 프롬프트는 고정 상태로 캐시되고, 동적 값은 유저 메시지 쪽에서 처리하는 구조입니다.

한 가지 더 짚자면, 현재 프롬프트 캐싱의 가장 큰 구조적 한계는 '완전 일치' 방식의 캐시 키 메커니즘 자체입니다. 시맨틱 캐싱(semantic caching)이나 부분 매칭 방식이 도입된다면 동적 컨텍스트가 섞인 복잡한 프롬프트에서도 캐싱 효과를 훨씬 잘 살릴 수 있을 것 같습니다. 시맨틱 캐싱이란 텍스트가 완전히 일치하지 않아도 의미가 유사하면 캐시를 재사용하는 방식입니다. 아직은 지원되지 않지만, 이 기능이 추가된다면 지금보다 훨씬 넓은 범위의 서비스에서 캐싱을 실용적으로 쓸 수 있을 것으로 보입니다.

프롬프트 캐싱은 분명 현존하는 API 비용 최적화 도구 중 단일 기능으로는 가장 강력한 편입니다. 다만 선언만 하면 끝나는 게 아니라, 프리워밍 전략과 히트율 모니터링, 동적 컨텐츠 분리까지 챙겨야 실제 절감 효과를 온전히 누릴 수 있습니다. 캐싱을 처음 도입한다면 usage 필드 로깅부터 먼저 붙이고 시작하는 것을 권장합니다. 숫자가 보여야 뭘 고쳐야 할지도 보이기 때문입니다.


참고:


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 ⚡ 정보 부스터 🚀