AI 에이전트의 청구서가 하룻밤 사이에 세 배로 늘어났습니다. 모델이 아니라 프롬프트 캐시가 망가졌습니다.
요약
AI 에이전트의 API 비용 급증 원인이 모델 자체가 아닌 프롬프트 캐시 관리 실패 때문임을 분석합니다. 캐싱은 요청 접두사가 '정확히' 일치해야 하며, 휘발성 정보(예: 현재 시간)를 가장 앞에 두면 캐시가 무효화되어 엄청난 비용을 초래할 수 있습니다.
핵심 포인트
- 캐싱은 정확한 접두사 일치를 요구하며, 의미적 유사성은 인정되지 않습니다.
- 휘발성 데이터는 반드시 캐시 브레이크포인트 이후에 배치해야 합니다.
- 정적인 내용(도구 스키마 등)을 앞에 두어 비용 절감 효과를 극대화할 수 있습니다.
아무도 모델을 건드리지 않았고, 트래픽도 건드리지 않았으며, 누가 봐도 프롬프트도 건드리지 않은 것 같았습니다. 그런데도 저희 에이전트의 API 청구서가 배포와 다음 배포 사이에 세 배로 늘어났고, 평균 응답 지연 시간(latency)은 첫 토큰까지 거의 두 배가 되었습니다.
모델 자체는 괜찮았습니다. 문제가 된 것은 프롬프트 캐시였는데, 저희가 '도움이 된다'고 추가한 단 한 줄 때문에 망가진 것이었습니다.
프롬프트 캐싱의 실제 작동 방식
대부분의 사람들은 프롬프트 캐싱을 마법 같은 할인 스위치처럼 취급합니다. 켜기만 하면 비용이 싸질 거라고 생각하죠. 하지만 실제로 이 기능은 요청의 바이트 단위로 동일한 접두사(prefix)를 이전 호출의 캐시와 일치시키는 것입니다. 이때, 사용자가 (또는 SDK가) 지정하는 중단점(breakpoint)까지 적용됩니다. 만약 접두사가 정확히 일치하면, 제공업체는 해당 토큰들을 모델의 어텐션 레이어(attention layers)를 통해 다시 처리하는 과정을 건너뛰고, 그 비용을 청구합니다. 이 비용은 보통 입력 비용의 10% 정도이며, 또한 해당 접두사에 대한 KV 캐시가 이미 계산되어 메모리에 존재하기 때문에 첫 토큰까지 걸리는 시간(time-to-first-token)도 크게 단축됩니다.
핵심은 '정확히' 일치해야 한다는 것입니다. '의미적으로 유사하다'거나 '대체로 같다'는 의미가 아닙니다. 중단점 이전 어느 곳에 토큰 하나만 다르면, 그 지점 이후의 전체 접두사는 일치하는 것으로 간주되지 않습니다 — 비록 99%가 이전 호출과 동일하더라도 말입니다.
실수
저희 에이전트의 시스템 프롬프트는 대략 다음과 같았습니다:
Current time: 2026-07-15T09:14:02Z
User timezone: America/Chicago
...
현재 타임스탬프를 맨 위에 두는 것이 합리적이라고 생각했습니다. 날짜에 대해 추론할 때 '지금'이 무엇을 의미하는지 모델에게 알려주는 것은 정말 유용하기 때문입니다. 하지만 이는 모든 요청의 가장 첫 토큰들이 매번, 심지어 초 단위까지도 고유하게 만들었습니다. 저희가 정적인 블록 뒤에 설정했던 캐시 중단점은 아무 소용이 없었습니다. 왜냐하면 일치 여부 검사가 첫 번째 토큰에서 실패했기 때문입니다. 이 18k 토큰 분량의 도구 정의(tool definitions)와 문서(docs) — 모든 호출에서 동일한 내용이었음에도 불구하고 — 매번, 몇 달 동안 완전히 재처리되고 완전히 청구되었던 것입니다.
우리는 누군가가 순전히 호기심으로 캐시 적중률(cache-hit-rate)을 측정 지표로 기록했기 때문에 그것을 알아차릴 수 있었습니다. 그 수치는 0%였습니다. '예상보다 낮다'는 수준이 아니었습니다. 아예 0이었던 것입니다.
해결책 (The fix)
캐시의 안정성에는 하나의 규칙이 있습니다. 모든 정적(static) 내용은 브레이크포인트(breakpoint) 이전에 가야 하고, 모든 휘발성(volatile) 내용은 그 이후에 가야 합니다. 끝입니다.
[STABLE PREFIX — cache breakpoint here]
도구 스키마 (tool schemas)
검색 문서 (retrieval docs)
...
만약 모델이 현재 시간을 알아야 한다면, 사용자 턴(user's turn) 바로 옆의 휘발성 접미사(volatile suffix)에 주입해야 합니다. 위치 제로에는 넣으면 안 됩니다. 이 재배치 과정은 우리에게 기능적인 측면에서 아무런 비용도 발생시키지 않았습니다. 하지만 그 대가로 우리는 매일 호출량에 따라 필요했던 것보다 3~10배나 높았던 청구서의 몇 달 치 금액을 지불해야 했습니다.
자신의 트래픽으로 계산해 보세요. 만약 하루 3,000건의 호출에서 18k 토큰 분량의 정적 블록을 전액 요금(full price)으로 보내는 것과, 약 10% 캐시된 가격으로 보내는 것을 비교한다면, 이는 매일 약 54M 토큰에 대해 전액 요금을 지불하는 것과 사실상 아무것도 캐싱하지 못해 전액 요금을 지불하는 것 사이의 차이입니다. 어떤 합리적인 토큰당 요율로 보아도, 이 격차는 단순한 반올림 오차가 아니라 청구서의 대부분을 차지합니다.
당신이 이 규칙을 알아도 걸릴 수 있는 부분 (The part that will bite you even if you know this rule)
프레임워크와 에이전트 SDK는 사용자가 자신의 프롬프트 템플릿을 올바르게 작성했더라도 조용히 이런 문제를 일으킬 것입니다. 흔한 두 가지 방식이 있습니다:
- 일부 미들웨어 계층(middleware layer)이 신중하게 순서가 지정된 시스템 프롬프트 앞에 자동으로 삽입하는 메타데이터 (타임스탬프, 요청 ID, 추적 ID).
- 비결정론적 순서 지정 — 딕셔너리(dict), 세트(set), 또는
캐시 적중률(cache-hit-rate)을 '있으면 좋은 것(nice-to-have)'이 아니라 일급 지표(first-class metric)로 다루세요. 이것은 단순히 성능 수치가 아닙니다. 대규모 정적 컨텍스트를 가지고 비자명한 수의 호출을 하는 모든 에이전트에게, 그것은 곧 청구서가 됩니다.
지난 몇 주 동안 아무도 알아채지 못한 채 조용히 당신의 캐시를 망가뜨린 것은 무엇인가요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기