토큰 아끼는 법을 검색하다가, 내가 얼마 쓰는지도 모른다는 걸 깨달았다
요즘 유튜브와 기술블로그를 보면 AI 토큰 이야기가 빠지지 않습니다. "컨텍스트를 아껴라", "프롬프트를 짧게 써라", "서브에이전트로 분리해라". 그런 글들을 읽으면서 저도 토큰을 얼마나 사용하고 있는지 궁금해졌습니다.
사용량은 이미 로컬에 있었다
처음에는 사용량을 어디서 구해야 하나 막막했습니다. Anthropic 콘솔? 훅을 심어서 직접 기록? 그런데 알고 보니 데이터는 이미 전부 제 컴퓨터에 있었습니다.
Claude Code는 모든 세션을 ~/.claude/projects/ 아래에 JSONL 파일로 남깁니다. 한 줄이 하나의 이벤트고, 그중 어시스턴트 응답 줄에는 이런 정보가 들어 있습니다.
{
"type": "assistant",
"timestamp": "2026-08-11T10:45:15.370Z",
"cwd": "/Users/me/dev/my-project",
"gitBranch": "feat/login",
"message": {
"model": "claude-fable-5",
"usage": {
"input_tokens": 2,
"output_tokens": 1938,
"cache_read_input_tokens": 16177,
"cache_creation_input_tokens": 19488
}
}
}
모델명, 네 종류의 토큰 수, 시각에 더해 어떤 프로젝트의 어떤 git 브랜치에서 작업했는지도 남아 있었습니다. 제 컴퓨터에는 한 달 반 동안 파일 265개, 총 135MB의 기록이 쌓여 있었습니다.
처음에는 모든 응답의 usage를 합산하면 될 줄 알았습니다.
하지만 끝이 아니었습니다.
같은 요청이 로그에 여러 번 남아 있었다
구현 전에 실제 로그를 전수조사하다가 이상한 걸 발견했습니다. 어시스턴트 레코드가 17,229건인데, 요청 ID(requestId)를 중복 제거하면 7,246건밖에 안 되는 겁니다.
같은 요청 ID를 가진 레코드들을 나란히 놓고 보니 이유가 보였습니다.
uuid 333c... 내용: [thinking 블록] usage: input 2, output 1424, ...
uuid 119a... 내용: [tool_use 블록] usage: input 2, output 1424, ... ← 완전히 동일
uuid 6ea8... 내용: [text 블록] usage: input 2, output 1424, ... ← 완전히 동일
Claude의 응답 하나는 여러 조각(생각, 도구 호출, 본문 텍스트)으로 이루어지는데, Claude Code는 이걸 조각마다 한 줄씩 기록합니다. 그런데 각 줄에 응답 전체의 usage가 복사돼 있었습니다. 즉, 10조각짜리 응답이면 같은 토큰 수가 10번 적혀 있는 겁니다.
이 표본에서는 응답 레코드 수가 고유 요청 수의 약 2.4배였습니다. 토큰 합계의 배율은 요청별 토큰 수와 중복 횟수에 따라 달라집니다.
이 표본에서는 같은 요청 ID의 usage가 같았으므로 첫 레코드만 세는 것으로 중복을 제거할 수 있었습니다. 다른 버전의 로그에서도 첫 레코드가 최종 사용량인지까지 보장되는 것은 아닙니다. 그리고 정말 제대로 셌는지 확인하려고, 제 파서를 전혀 거치지 않는 독립 검증을 붙였습니다. jq로 원본 로그에서 직접 고유 요청 수를 세서 DB의 레코드 수와 비교하는 거죠.
$ find ~/.claude/projects -name '*.jsonl' | xargs cat \
| jq -r 'select(.type=="assistant") | .requestId' | sort -u | wc -l
7360
$ sqlite3 ~/.tokkaebi/data.db "SELECT COUNT(*) FROM usage_records;"
7360
처음 살펴본 7,246건과 위 검증의 7,360건은 수집 시점이 다른 기록입니다. 두 숫자를 서로 대조하는 검증은 아닙니다. 실제 비교에서는 입력 파일 목록과 집계 종료 시각을 고정해야 합니다.
레코드 수가 같다는 것만으로 금액까지 맞았다고 결론 내릴 수는 없습니다. 요청 ID가 없는 레코드를 전부 하나로 합치지 않는지, 같은 ID에서 usage가 달라질 때 어떤 값을 고르는지, 재수집해도 중복 삽입되지 않는지를 따로 검증해야 합니다. ID가 없는 기록은 식별 가능한 보조 키를 사용하거나 별도의 미집계 목록으로 남겨야 합니다.
검증용 작은 표본은 세 종류면 시작할 수 있습니다. 같은 ID·같은 usage인 세 줄은 한 요청이 되어야 합니다. 같은 ID·다른 usage인 두 줄은 충돌로 드러나야 합니다. ID가 없는 두 줄은 서로 다른 기록일 수 있으므로 조용히 하나로 줄어들면 안 됩니다. 행 개수 검증에 더해 입력·출력·캐시 읽기·캐시 쓰기 각각의 합계도 원본과 대조해야 합니다.
캐시 읽기 2,846만 토큰의 정체 — 프롬프트 캐싱 딥다이브
파서를 완성하고 하루치 사용량을 출력했을 때는 숫자가 잘못 나온 줄 알았습니다.
모델 입력 출력 캐시 읽기 캐시 쓰기 비용
claude-fable-5 372 216,724 28,468,974 1,203,107 $62.36
입력이 372 토큰인데 캐시 읽기가 2,846만? 비율로 따지면 캐시 읽기가 입력의 7만 배가 넘습니다. 처음엔 제 파서가 또 뭘 잘못 세는 줄 알았는데, 파고들어 보니 이게 정상이었습니다.
LLM API는 매번 처음 만나는 사이다
여기서 다루는 Messages API 요청은 필요한 대화 문맥을 함께 전달하는 방식입니다. 모델이 이전 요청의 대화를 자동으로 기억한다고 가정할 수 없습니다. 다만 클라이언트가 항상 원문 전체를 보내는 것은 아닙니다. 오래된 문맥을 요약하거나 제외할 수 있으므로, 실제 전송량은 세션 관리 방식에 따라 달라집니다.
요청에 포함하는 문맥에는 시스템 프롬프트, 도구 정의, 대화 메시지가 있습니다. 파일 내용과 도구 실행 결과도 다음 판단에 필요한 문맥이 될 수 있습니다. 원문을 계속 유지하는 동안에는 뒤의 요청에서도 같은 내용을 다시 처리하지만, 요약이나 문맥 정리가 일어나면 전송하는 내용은 달라집니다.
제 하루 기록의 캐시 읽기 28,468,974토큰을 아래 표의 일반 입력 단가인 100만 토큰당 $10으로 계산하면 약 $284.69입니다. 캐시 읽기 단가 $1로 계산하면 약 $28.47입니다. 같은 토큰 수라도 어느 항목에 잡히는지에 따라 비용 해석이 달라졌습니다.
서버가 "앞부분"을 기억해주는 거래
그래서 프롬프트 캐싱이 있습니다. 아이디어는 단순한 거래입니다.
"방금 처리한 프롬프트의 앞부분을 서버가 잠시 보관해줄게. 다음 요청에서 앞부분이 완전히 똑같으면, 그 부분은 다시 처리하지 않고 이어서 갈게. 대신 보관비를 조금 받고, 재사용할 때는 정가의 10%만 받을게."
여기서 중요한 건 "앞부분(prefix)"이라는 조건입니다. 캐시 지점까지의 프롬프트 prefix가 일치해야 재사용할 수 있습니다. 도구 정의·시스템 프롬프트·메시지 순서로 앞부분을 구성하고, 앞선 문맥을 유지한 채 새 대화를 뒤에 붙이는 경우에 이점을 얻습니다. 앞의 내용을 수정하거나 요약하면 재사용할 수 있는 범위도 달라집니다.
1번째 요청: [도구 정의][시스템 프롬프트][대화 A]
→ 전체를 처리하고 캐시에 저장 (쓰기)
2번째 요청: [도구 정의][시스템 프롬프트][대화 A][대화 B]
└────────── 캐시에서 읽음 (10%) ─────────┘└─ 새로 처리 ─┘
3번째 요청: [도구 정의][시스템 프롬프트][대화 A][대화 B][대화 C]
└────────────── 캐시에서 읽음 (10%) ──────────────┘└─ 새로 처리 ─┘
이제 앞에서 본 숫자를 해석할 수 있습니다. 하루 동안 입력 372, 캐시 읽기 2,846만이 기록됐다는 뜻은 다음과 같습니다.
- 캐시를 읽지도 새로 쓰지도 않은 입력이 372 토큰입니다. 새로 입력한 메시지 전체와 같은 값은 아닙니다. 새 메시지가 캐시 생성 구간에 포함될 수도 있습니다.
- 약 2,846만 토큰은 이전에 캐시한 prefix를 재사용한 양입니다. 대화뿐 아니라 도구 정의와 시스템 프롬프트도 포함될 수 있습니다.
턴이 거듭될수록 대화가 길어지니 캐시 읽기는 눈덩이처럼 커집니다. 그러니까 캐시 읽기 항목은 "뭔가 이상한 숫자"가 아니라, 캐시가 없었다면 정가로 청구됐을 트래픽을 보여주는 항목인 겁니다.
한 가지 주의점도 있습니다. 앞부분의 내용이 달라지면 그 이후까지 포함하는 prefix는 더 이상 같은 캐시 항목에 적중하지 않습니다. 예를 들어 시스템 프롬프트에 현재 시각 같은 걸 박아 넣으면 매 요청의 앞부분이 달라져서 캐시가 한 번도 안 먹습니다. LLM 응용을 직접 만들 때 "변하는 값은 프롬프트 뒤쪽에" 배치하라는 이유가 이것입니다.
5분 캐시와 1시간 캐시 — 보관비의 차이
"캐시 쓰기"는 서버에 보관을 맡길 때 드는 비용입니다. 요금은 보관 기간에 따라 달라집니다.
| 항목 | 단가 (입력 정가 대비) | fable-5 기준 ($/100만 토큰) |
|---|---|---|
| 입력 (정가) | 100% | $10.00 |
| 캐시 읽기 | 10% | $1.00 |
| 캐시 쓰기 (5분 보관) | 125% | $12.50 |
| 캐시 쓰기 (1시간 보관) | 200% | $20.00 |
쓰기가 정가보다 비싼 게 눈에 띄죠. 캐시에 넣는 순간에는 오히려 손해입니다. 따라서 캐시를 자주 사용하는 환경이어야 경제적입니다. 쓰기는 한 번이지만 읽기는 재사용할 때마다 발생하니, 두세 턴만 이어져도 본전을 뽑고 그 뒤로는 전부 절감입니다.
5분과 1시간의 차이는 "대화 사이의 공백을 얼마나 버티느냐"입니다. 5분 캐시는 마지막으로 쓰거나 재사용한 시점부터 5분이 지나면 만료됩니다. 1시간 캐시는 더 긴 공백을 견디지만, 그만큼 쓰기 단가가 높습니다. 제 로그에서는 캐시 쓰기의 56%가 5분, 44%가 1시간 캐시였습니다. 두 종류를 함께 쓰고 있었습니다.
집계할 때도 주의가 필요했습니다. 로그의 usage에는 두 종류를 합산한 필드와 각각 기록한 필드가 모두 있습니다. 이를 중복해서 더하면 쓰기 비용이 두 배로 계산됩니다.
그래서, 얼마나 아꼈나
이제 절감액은 아래 식으로 계산할 수 있습니다.
캐시 절감 추정 = 읽기 토큰 × (일반 입력 단가 − 읽기 단가)
− 쓰기 토큰 × (쓰기 단가 − 일반 입력 단가)
최근 12주 데이터를 계산한 결과입니다.
$ tokkaebi cache
캐시 분석 · 최근 12주
히트율 99.9% (캐시 읽기 1,280,764,264 / 신규 입력 823,242)
쓰기 비중 5분 56% · 1시간 44%
순절감 $9,186.25 (읽기 절감 $9,456.04 − 쓰기 프리미엄 $269.78)
12주간 캐시로 처리한 토큰은 약 12억 8천만 개였습니다. 캐시가 없었다면 약 9,500달러를 더 냈어야 했고, 쓰기 프리미엄 약 270달러를 차감한 절감 추정값이 약 9,186달러였습니다. 이는 기록의 토큰 수에 API 단가를 적용해 계산한 비교값입니다. 구독 요금제에서 실제로 그 금액을 결제했거나 환급받았다는 의미는 아닙니다.
줄이기 전에 얼마나 쓰는지부터 확인했다
비용과 사용량을 같은 숫자로 보여주면 오해가 생긴다
같은 100만 토큰도 입력인지 출력인지, 캐시를 읽었는지 썼는지에 따라 비용이 달라집니다. 예를 들어 일반 입력 단가를 1로 놓고 캐시 읽기를 0.1, 쓰기를 1.25라고 가정해보겠습니다. 같은 길이의 문맥을 한 번 쓰고 한 번 읽으면 비용은 1.35이고, 두 번 새로 처리하면 2입니다. 절감은 0.65입니다. 쓰기 비용 전부를 손해로 빼면, 원래도 들어갔을 입력 비용까지 두 번 차감하게 됩니다.
이 가정은 앞 표의 5분 캐시 계산을 설명하는 예입니다. 모든 모델과 시기에 통용되는 단가가 아니므로 집계에는 모델명과 적용한 가격표의 기준일을 함께 남겨야 합니다. 원본 토큰 수를 보존하면 가격표를 고친 뒤 금액을 다시 계산할 수도 있습니다.
히트율에도 분모가 필요합니다. 입력 토큰 기준 재사용 비율을 보려면 캐시 읽기 / (일반 입력 + 캐시 생성 + 캐시 읽기)처럼 정의할 수 있습니다. 요청 중 캐시를 한 번이라도 사용한 요청의 비율과는 다른 지표입니다. 위 CLI의 99.9%는 표시된 읽기와 신규 입력만으로 만든 수치여서, 전체 입력 대비 재사용 비율로 그대로 해석하면 안 됩니다.
완성된 tokkaebi는 이렇게 생겼습니다.
$ tokkaebi today
오늘 사용량 · 2026-08-11 (화) · $87.27
모델별
┌────────────────┬───────┬─────────┬────────────┬───────────┬────────┬──────────┐
│ 모델 │ 입력 │ 출력 │ 캐시 읽기 │ 캐시 쓰기 │ 비용 │ │
│ claude-fable-5 │ 2,316 │ 316,010 │ 44,645,376 │ 1,337,083 │ $86.88 │ ▮▮▮▮▮▮▮▮ │
│ claude-opus-5 │ 28 │ 35 │ 329,533 │ 36,817 │ $0.40 │ ▮▯▯▯▯▯▯▯ │
└────────────────┴───────┴─────────┴────────────┴───────────┴────────┴──────────┘
프로젝트 · 브랜치별
│ tokkaebi │ main │ ... │ $43.14 │ ▮▮▮▮▮▮▮▮ │
│ guksu-trading │ fix/ops-hardening │ ... │ $12.69 │ ▮▮▯▯▯▯▯▯ │
💰 캐시 절감 $390.21 — 캐시가 없었다면 오늘 $477.48
🧌 연속 사용 2일째
- 어떤 프로젝트의 어떤 브랜치가 돈을 먹는지, 서브에이전트와 스킬별 비용까지 나옵니다
- 요일×시간대 히트맵을 찍어보니 저는 새벽형이 아니라 목요일 오후형이더군요
- 월 예산을 설정하면 "이 속도면 월말 $312" 같은 페이스 예측이 붙습니다
- 데이터는 전부 로컬 SQLite에만 저장됩니다. 프롬프트 원문은 저장하지 않고, 외부 전송도 없습니다
설치는 한 줄입니다.
npm install -g tokkaebi
tokkaebi today
훅이 아니라 원본 로그를 읽는 방식이라, 오늘 설치해도 설치 전 과거 기록까지 소급해서 집계됩니다. Claude Code는 기본 30일이 지난 로그를 지우는데, tokkaebi는 수집한 기록을 자체 DB에 보존하므로 꾸준히 쓰면 30일 너머의 이력도 남습니다.
코드는 다음에서 확인할 수 있습니다: github.com/Guksu/tokkaebi
더 확인할 자료
- Anthropic Prompt caching — usage 필드, 캐시 생성·읽기 단가와 TTL. 본문의 개인 로그 수치와 API 단가 비교는 구분해서 읽어야 합니다.