Code 실전

Claude API 프롬프트 캐싱으로 API 비용 89% 줄이는 법 — Mac Mini 클러스터 실측

seunghyeonlab 2026. 5. 11. 18:02

hero

같은 CLAUDE.md를 매번 새로 전송하고 있다면, 지금 당장 돈을 버리고 있는 것이다. Claude Code를 반복 호출하는 자동화 환경에서 Prompt Caching을 제대로 설계하면 실측 기준 89% 비용 절감이 가능하다. Mac Mini 4대 n8n 클러스터에서 직접 확인한 수치다.

전체 캐싱 아키텍처 흐름


문제 정의 — 같은 내용을 왜 매번 보내고 있는가

Claude Code를 쓰다 보면 자연스럽게 CLAUDE.md를 두껍게 만든다. 프로젝트 구조, 코딩 컨벤션, 자주 쓰는 명령어까지 다 때려 넣으면 금방 8,000토큰을 넘는다.

문제는 반복 호출이다. n8n 워크플로우가 하루에 수십 번 claude analyze를 호출하면, 매번 그 8,000토큰을 고스란히 API로 재전송한다. 서버는 이미 그 내용을 봤는데도 매번 새로 읽는다. 티켓팅 사이트에서 같은 좌석을 초 단위로 재조회하는 것과 다르지 않다.

처음에는 그냥 넘겼다. 하루 비용이 몇 달러 수준이면 티가 안 나기 때문이다. 그런데 워커를 4대로 늘리고 자동화 파이프라인이 복잡해지면서 월 단위 청구서가 눈에 띄게 불어났다. 그때 Prompt Caching을 제대로 파기 시작했다.

캐싱 전후 토큰 전송 비교


캐시 히트 구조 설계 — 핵심은 순서다

Claude API의 Prompt Caching은 접두 토큰(prefix token)을 서버에 5분간 보관한다. 이 캐시가 히트하려면 변하지 않는 내용을 앞에, 매번 바뀌는 내용을 뒤에 배치해야 한다.

캐시 가능한 블록 배치 우선순위:
1. system prompt (CLAUDE.md, 프로젝트 컨텍스트)
2. tools 정의 배열
3. 긴 참조 문서 (API 스펙, 스키마 등)
4. user 메시지 — 항상 맨 마지막

실제 코드로 고정하면 이렇다.

import anthropic

client = anthropic.Anthropic()

# CLAUDE.md 전체를 캐시 블록으로 고정
with open('CLAUDE.md', 'r') as f:
    project_context = f.read()

response = client.messages.create(
    model='claude-opus-4-5',
    max_tokens=1024,
    system=[
        {
            'type': 'text',
            'text': project_context,
            'cache_control': {'type': 'ephemeral'}  # 캐시 앵커 지정
        }
    ],
    messages=[
        {'role': 'user', 'content': '이 함수의 버그를 찾아줘'}
    ]
)
print(response.usage)  # cache_read_input_tokens 확인

cache_read_input_tokens 값이 0이면 캐시 미스다. 첫 호출은 반드시 미스이고, 두 번째 호출부터 히트가 나기 시작한다.


Mac Mini 클러스터 실측 — $6.00에서 $0.63으로

Mac Mini 4대 n8n 워커에서 Claude Code를 병렬 호출하는 구조로 50회 반복 실측했다. CLAUDE.md 8,000토큰짜리 대형 프로젝트 기준이다.

구분 캐싱 OFF 캐싱 ON
총 입력 토큰 400,000 20,000 (실청구)
캐시 히트 토큰 380,000
비용 $6.00 $0.63
절감률 89%

캐시 히트 토큰은 90% 할인이 적용된다. 실제 사용량 로그를 찍어보면 이렇게 나온다.

usage = response.usage
print(f'캐시 히트: {usage.cache_read_input_tokens} 토큰')
print(f'신규 입력: {usage.input_tokens} 토큰')
print(f'캐시 생성: {usage.cache_creation_input_tokens} 토큰')
# 캐시 히트: 7980 토큰
# 신규 입력: 420 토큰
# 캐시 생성: 0 토큰

cache_creation_input_tokens가 0이면 이미 캐시가 살아있다는 뜻이다. 이 숫자 세 개를 모니터링하는 것만으로 캐싱이 제대로 동작하는지 실시간으로 파악할 수 있다.

캐시 생성과 히트 순서


캐시가 깨지는 세 가지 함정

Prompt Caching을 켰는데도 캐시 히트가 안 나면 대부분 이 세 가지 중 하나다.

첫째, 캐시 블록 앞에 동적 내용 삽입. 현재 시간, 랜덤 세션 ID 같은 값을 system prompt 앞부분에 붙이는 순간 매번 미스가 된다. 캐시 키는 접두 토큰 전체의 해시이기 때문에, 앞 한 글자만 바뀌어도 뒤에 아무리 동일한 내용이 있어도 새 캐시로 취급된다.

# 잘못된 예 — 캐시 매번 무효화
system = [
    {'type': 'text', 'text': f'현재 시간: {datetime.now()}'},  # 동적 내용 앞배치
    {'type': 'text', 'text': project_context, 'cache_control': {'type': 'ephemeral'}}
]

# 올바른 예 — 동적 내용은 user 메시지로
system = [
    {'type': 'text', 'text': project_context, 'cache_control': {'type': 'ephemeral'}}
]
messages = [{'role': 'user', 'content': f'현재 시간 {datetime.now()} 기준으로 분석해줘'}]

둘째, 5분 TTL 초과. 배치 작업이 5분을 넘기는 구간이 생기면 중간에 캐시가 만료된다. 워커가 여럿이면 첫 번째 워커가 캐시를 생성하고, 나머지가 5분 안에 히트를 가져가는 구조로 설계해야 한다. 긴 배치라면 중간에 의도적으로 캐시 워밍업 호출을 넣는 방법도 있다.

셋째, tools 배열 순서 변동. tool 배열이 매 호출마다 다른 순서로 넘어오면 캐시 키가 달라진다. 특히 동적으로 tool을 구성하는 코드에서 자주 발생한다. tool 배열은 항상 고정된 이름 순으로 정렬해두는 것이 안전하다.

캐시 무효화 원인 분류


클러스터 전체에 캐싱 전파하기

Mac Mini 4대 구조에서 n8n HTTP Request 노드는 각각 독립적으로 API를 호출한다. 독립 호출이라도 서버 사이드 캐시는 API 키 단위로 공유된다. 같은 API 키를 쓰는 한, 어느 워커가 먼저 캐시를 생성하든 나머지 워커가 히트를 따 먹는다.

이 구조를 '선발대 워커'라고 부르기로 했다. 배치가 시작될 때 워커 1이 먼저 system prompt를 전송해서 캐시를 생성하고, 이후 워커 2~4는 5분 안에 동일 API 키로 호출해서 히트를 가져간다. 팀 전체가 같은 API 키를 쓰는 환경이라면, 누군가 한 명이 먼저 호출한 캐시를 팀 전체가 공유하는 셈이다.


마무리

Prompt Caching은 cache_control 한 줄 추가가 아니다. 변하지 않는 것을 앞에, 매번 바뀌는 것을 뒤에 배치하는 설계 원칙이 전제다. Mac Mini 클러스터 실측에서 89% 비용 절감이 나왔다. 캐시 구조를 한 번 제대로 잡아두면, 같은 코드베이스를 쓰는 팀 전체가 혜택을 나눈다.

다음 글에서는 이 캐싱 구조 위에 멀티 에이전트 오케스트레이션을 올리는 방법을 다룬다.


🐦 X에서 더 빠르게: @baegseungh7061
📚 이 시리즈 더 보기: Code 실전
💌 새 글 알림: X 팔로우 또는 블로그 RSS 구독