Claude Code로 22시간 동안 2억 4,600만 토큰을 소모하며 모든 토큰의 행방을 추적한 결과, 놀라운 사실을 발견했습니다.
요약
Claude Code 사용 중 발생하는 막대한 토큰 소모 원인을 분석한 결과, 실제 출력보다 컨텍스트 재작성 및 캐시 무효화가 비용의 핵심임을 밝혀냈습니다. 이미지 삽입, 모델 전환, 하위 에이전트 생성 등이 캐시를 재작성하여 비용을 급증시키는 주요 요인으로 확인되었습니다.
핵심 포인트
- 비용의 핵심은 컨텍스트 크기가 아닌 캐시 무효화(cache invalidation)
- 이미지 붙여넣기 및 모델 전환 시 대규모 캐시 재작성 발생
- 수동 /compact 명령 사용 시 호출당 비용 약 10배 절감 가능
- 자동 압축(Auto-compact)은 비용 절감에 효과적이지 않음
요약(TL;DR): 저는 제가 불공정하게 요금이 부과되고 있다고 생각했습니다 ($100 Max 플랜 사용 중, 5시간 제한 시간의 46%가 약 20분 만에 소진됨). 그래서 Claude가 자신의 세션 트랜스크립트(session transcript)를 분석하도록 했습니다. 분석 결과: 소비된 2억 4,600만 토큰 중 실제 출력(output)은 0.13%에 불과했습니다. 나머지는 모든 도구 호출(tool call) 시마다 컨텍스트(context)를 다시 읽고 다시 쓰는 데 사용되었습니다. 실제 비용을 유발하는 핵심 요인은 컨텍스트 크기가 아니라 캐시 무효화(cache invalidation)였습니다. 캐시 쓰기(cache writes)는 원시 토큰의 14%를 차지했지만, 실제 비용의 65~75%를 차지했습니다. 가장 비용이 많이 드는 작업은 다음과 같습니다: 큰 세션에 이미지 붙여넣기 (이미지 한 번 붙여넣기로 약 59만 토큰의 캐시 재작성 발생), 세션 중간에 모델 전환 (모델은 캐시 키(cache key)의 일부이므로 전체 접두사(prefix)가 재작성됨), 그리고 큰 컨텍스트를 가진 하위 에이전트(subagents)를 생성하거나 부활시키기 (에이전트당 전체 컨텍스트 쓰기 1회 발생). 자동 압축(Auto-compact)은 도움이 되지 않습니다. 이는 컨텍스트 한계치에 도달할 때쯤 트리거되므로, 세션이 57만 토큰 상태로 수백 번의 호출을 유지하며 최대 비용을 지불할 수 있습니다. 수동 /compact 명령은 호출당 비용을 약 10배 절감해 주었습니다. 세션 중 가장 생산적이었던 5시간 동안이 가장 저렴하기도 했습니다. 비용은 작업량(how much work gets done)이 아니라 컨텍스트 크기를 따라갑니다. 모든 사람의 문제가 이와 같지는 않을 것입니다. 만약 활동이 전혀 없는데 세션이 55% 사용된 상태로 시작하거나 리셋(reset)이 전혀 이루어지지 않는다면, 그것은 서버 측 문제이며 어떤 워크플로 조언으로도 해결할 수 없습니다. 아래에 자신의 트랜스크립트를 측정하여 어떤 범주에 속하는지 알려주는 스크립트가 있습니다. 전체적인 메커니즘, 수치, 그리고 복사-붙여넣기 가능한 측정 스크립트는 아래에 있습니다. 방법론에 대한 투명성: 저는 이 모든 것을 스스로 알아낸 것이 아닙니다. 저는 그저
어느 시점에는 약 20분 만에 컨텍스트 윈도우 (window)의 약 46%를 소모했고, 제가 불공정하게 과금되고 있다고 가정했습니다. 그래서 Claude가 자신의 세션 트랜스크립트 (session transcript)를 파싱하고 측정하도록 했습니다. Claude Code는 모든 API 응답에 대한 사용 기록을 ~/.claude/projects/<project>/<session-id>.jsonl에 기록합니다. 첫 번째 측정 시도를 2배나 틀리게 만들었던 측정 함정 (measurement trap)을 포함하여, 그 결과는 다음과 같습니다. 본격적인 내용에 앞서 주의사항을 말씀드리자면, 이 게시물은 메가스레드 (megathread)에 가득한 고통스러운 문제들을 모두 설명하지는 않습니다. 이것은 단 하나의 세션이며, 저의 워크플로 (workflow)를 기준으로 한 것입니다. 그리고 저의 워크플로는 특이치 (outlier)입니다 (스크린샷으로 구동되는 4개의 iOS 시뮬레이터 사용). 만약 아무것도 입력하지 않았는데 세션 사용량이 55%에서 시작하거나, 리셋 (reset)이 며칠 동안 "0 min"에 멈춰 있거나, 작업량이 변하지 않았는데 주간 사용량이 한 시간 만에 60% 급증했다면, 그것은 이 게시물에서 다루는 내용이 아닙니다. 그것은 서버 측의 과금 (metering) 문제로 보이며, Anthropic이 이전에 확인하고 수정한 사례도 있는 문제들로, 어떤 워크플로 조언으로도 해결할 수 없습니다. 이 게시물이 여러분께 제공하는 것은 여러분이 어느 범주에 속하는지 판단할 수 있는 도구입니다. 만약 여러분의 트랜스크립트 계산 결과가 계정 UI에 표시된 소비량과 대략 일치한다면, 과금 시스템은 여러분의 워크플로를 측정하고 있는 것이며 아래의 해결책들이 적용됩니다. 만약 UI에 표시된 소비량이 트랜스크립트로 설명되지 않는다면, 그것은 워크플로 문제가 아니라 버그 리포트 (bug report) 대상이며, 이제 여러분은 리포트를 제출할 수 있는 수치를 확보한 것입니다.
첫째: 측정 함정 (measurement trap). 각 API 응답은 JSONL에 여러 줄로 기록되는데, 각 콘텐츠 블록 (content block: thinking, text, tool_use)마다 한 줄씩 작성됩니다. 이 모든 줄은 동일한 사용 객체 (usage object)의 복사본을 가지고 있습니다. 만약 단순히 줄 단위로 사용량을 합산한다면, 각 요청을 2~3번씩 중복 계산하게 됩니다. 저의 첫 번째 계산에서는 1,139개의 요청이 나왔습니다. message.id를 기준으로 중복을 제거하면 (스트리밍 시 동일한 id를 다시 쓰기 때문에 id당 최대 output_tokens를 유지함), 553개가 되었습니다.
key = msg.get('id') or rec.get('requestId')
if key in seen: continue # <-- 이 부분이 없으면 모든 수치가 약 2배 부풀려집니다
이곳에 게시된 "내가 얼마나 사용했는지"를 보여주는 모든 스크립트는 이 부분을 잘못 처리하고 있습니다. 만약 여러분이 스스로 계산한 수치 때문에 겁을 먹고 있었다면, 이 부분을 먼저 확인하십시오.
수정된 총계: 22시간, 553회 요청
| 구분 | Raw Tokens (원시 토큰) | 점유율 |
|---|---|---|
| Cache read (캐시 읽기) | 212,107,694 | 85.9% |
| Cache write (캐시 쓰기) | 34,408,898 | 13.9% |
| Fresh input (신규 입력) | 1,028 | ~0% |
| Output (출력) | 309,883 | 0.13% |
| Total (합계) | 246,827,503 |
출력(Output)은 전체의 0.13%에 불과했습니다. 22시간 동안의 작업 중 모든 코드 한 줄, 모든 설명, 모든 커밋 메시지를 합쳐도 31만(310k) 토큰뿐이었습니다. 나머지 99.87%는 이동 중인 컨텍스트(Context)였습니다.
실제 발견한 사실: Raw tokens(원시 토큰)는 잘못된 단위입니다. Cache reads(캐시 읽기)는 기본 입력(Base input)의 약 0.1배로 청구됩니다. Cache writes(캐시 쓰기)는 기본 5분 TTL(Time To Live) 기준 1.25배, 또는 (Claude Code의 긴 세션이 사용하는) 1시간 TTL 기준 2배로 청구됩니다. 어느 쪽이든 두 캐시 방향 사이에는 12.5~20배의 차이가 발생합니다.
따라서 가중치를 다시 적용하면 다음과 같습니다:
| 구분 | 1.25x 쓰기 가중치 적용 시 | 2x 쓰기 가중치 적용 시 |
|---|---|---|
| Cache write (캐시 쓰기) | 65.4% | 75.1% |
| Cache read (캐시 읽기) | 32.2% | 23.2% |
| Output (출력) | 2.4% | 1.7% |
캐시 쓰기(Cache writes)는 제 원시 토큰의 14%에 불과했지만, 실제 비용의 65~75%를 차지했습니다. 저는 이틀 동안 엉뚱한 것을 최적화하며 시간을 보냈습니다. 저는 컨텍스트 크기(Cache reads)를 걱정했지만, 실제로 제 할당량(Quota)을 갉아먹고 있었던 것은 컨텍스트 무효화(Context invalidation, Cache writes)였습니다. 이 둘은 해결 방법이 다른 별개의 문제입니다.
캐시 무효화(Cache invalidation)가 어떻게 발생하는지 예시를 보겠습니다.
일반적인 요청, 캐시가 따뜻한 상태(Cache warm):
cWrite: 383, cRead: 617,993 <- 저렴함
cWrite: 554, cRead: 618,376 <- 저렴함
그다음 제가 채팅창에 스크린샷을 하나 붙여넣었습니다:
cWrite: 589,235, cRead: 29,940 <- 59만(590k) 토큰의 접두사(Prefix) 전체가 다시 쓰임
단 한 번의 붙여넣기로 589,235 토큰이 쓰기 요율(1.252배)로 적용되어 약 73.7만118만 토큰의 입력 상당액(Input-equivalents)이 발생했습니다. 이는 일반적인 따뜻한 캐시 상태의 턴(Turn)에서 발생하는 수백 토큰의 쓰기 비용과 비교했을 때, 단 한 번의 동작으로 일반적인 턴 1,000회 분량의 쓰기 비용, 또는 60만(600k) 전체 캐시 읽기 12회 분량의 비용이 발생한 것입니다.
Claude Code 바이너리를 파헤쳐 보니, 캐시 키(Cache key)는 긴 목록에 대한 해시(Hash)로 구성되어 있습니다. 이 중 하나라도 변경되면 전체 접두사(Prefix)가 무효화됩니다:
systemHash, toolsHash, cacheControlHash, model, fastMode, globalCacheStrategy, betas, autoModeActive, isUsingOverage, cacheDiagnosis, effortValue, extraBodyHash, anyDeferLoading, messageHashes
여기에 포함된 항목들을 주목하십시오: model, effortValue, betas, toolsHash.
이것은 제가 나중에 다시 다루게 될 잔혹한 함의를 담고 있습니다. 세션이 5시간 단위의 버킷(bucket)으로 나뉜 5시간 구간(마지막 요청을 기준으로 고정됨; 버킷 2는 제가 자고 있었기 때문에 비어 있음. 이 구간들은 Anthropic의 실제 윈도우 경계가 아니라 세션 상대적 슬라이스임):
| 구간 | 요청 수 (reqs) | 원시 토큰 (raw tokens) | 가중 평균 컨텍스트 (weighted avg context) | 출력 (output) |
|---|---|---|---|---|
| 4 | 154 | 41,559,013 | 5,192,443 | 269,217 |
| 3 | 164 | 79,894,818 | 21,591,975 | 486,646 |
| 1 | 169 | 95,835,761 | 27,647,605 | 566,691 |
| 0 | 66 | 29,537,911 | 11,339,283 | 446,609 |
윈도우 4와 윈도우 1을 비교해 보십시오: 요청 수는 거의 동일하지만 (154 대 169), 가중 비용은 5.3배 더 높습니다. 차이점은 수행된 작업의 양이 아니었습니다. 평균 컨텍스트 (average context)가 269k에서 567k로 증가했기 때문에, 모든 요청의 비용이 더 많이 들었고, 모든 무효화 (invalidation)를 복구하는 데 더 많은 비용이 들었습니다. 그리고 출력을 보십시오: 윈도우 4는 전체 세션 중 가장 많은 출력(99k 토큰, 실제 작성된 코드 중 가장 많음)을 생성하면서도 가장 낮은 비용(5.2M 가중치)을 기록했습니다. 윈도우 1은 5.3배의 비용을 들여 출력을 35% 적게 생성했습니다. 비용은 생산성이 아니라 컨텍스트 크기를 따릅니다. 가장 생산적인 윈도우가 가장 저렴한 윈도우였습니다.
자동 압축 (auto-compact)이 왜 저를 구원하지 못했는가
자동 압축 (auto-compact)은 절대적인 크기가 아니라 컨텍스트 윈도우 (context window)의 점유율에 따라 트리거됩니다. 저의 컨텍스트는 약 1M 윈도우로 보이는 환경에서 ~570k에 머물러 있었습니다. 이는 약 57%가 채워진 상태입니다. 자동 압축은 한계치(ceiling) 근처에서 작동합니다. 따라서 저는 안전망이 전혀 작동하지 않는 상태에서, 호출당 최대 운송비 (maximum freight)를 지불하며 수백 번의 요청 동안 570k 상태로 머물러 있었습니다. 뒤틀린 결론: 더 큰 컨텍스트 윈도우가 저의 할당량 (quota)을 더 심하게 소모하게 만들었습니다. 200k 윈도우였다면 180k 부근에서 강제로 압축되었을 것이고 호출당 비용도 3분의 1 수준이었을 것입니다. 1M 윈도우는 제가 570k에서 무기한으로 정체될 수 있게 만들었습니다.
오래된 도구 결과(tool results)를 점진적으로 트리밍하는
"[Old tool result cleared]" // text: 디스크에 기록됨, 재읽기 가능. 이는 도구 결과(tool results)에만 영향을 미치며, 사용자의 메시지나 어시스턴트의 추론(reasoning)에는 절대 영향을 주지 않습니다. 이미지는 완전히 삭제(hard-cleared)되지만, 텍스트는 디스크에 유지되어 재읽기가 가능합니다. 스크린샷 작업이 많은 경우 이는 거의 이상적인 방식입니다. 제 환경에는 DISABLE_MICROCOMPACT=1이 설정되어 있었습니다 (제 설정이 아닌 Claude Desktop 호스트에 의해 주입됨). 주의사항: 257MB 크기의 CLI 바이너리 어디에서도 해당 문자열을 찾을 수 없었기에, 소스 코드를 통해 실제로 효과가 있었는지 증명할 수는 없습니다. 다만 제가 확실히 말할 수 있는 것은, 회수 가능한 토큰이 20k를 훨씬 상회했음에도 불구하고 그 어떤 것도 트리밍(trimming)되지 않았다는 점입니다.
제 토큰이 실제로 어디로 갔는가?
223 iOS 시뮬레이터 제어 (탭/스크린샷/스와이프)
205 Bash
38 SQL (MCP를 통해)
35 Read
17 Edit
트랜스크립트(transcript) 내 139개의 고유 이미지.
저는 스크린샷을 통해 4개의 iOS 시뮬레이터를 구동하며, 상태 전이(state transitions)를 확인하기 위해 UI를 탭하며 진행했습니다. 모든 스크린샷은 컨텍스트(context)에 포함되었고, 이후의 모든 요청에서 영구적으로 재읽기되었습니다. 요청 100번째에 찍은 스크린샷이 요청 500번째에서도 여전히 비용을 발생시키고 있었습니다. 동일한 상태 정보가 그동안 Postgres에 계속 머물러 있었습니다. 38개의 SQL 쿼리만으로도 223개의 시뮬레이터 호출이 요구하던 거의 모든 것에 답할 수 있었을 것이며, 이는 수천 개의 토큰이 영구적으로 소모되는 대신 각각 약 200개의 토큰만으로 가능했을 것입니다.
과거의 나에게 해주고 싶은 말
- 스크린샷은 일회성 비용이 아닙니다. 이미지 비용은 이미지 크기에 세션의 남은 턴(turn) 수를 곱한 만큼 발생합니다. 스크린샷을 구매가 아닌 구독처럼 예산을 관리하세요.
- UI가 아닌 데이터베이스를 쿼리하세요. 확인하려는 상태가 데이터 저장소(datastore)에 있다면 그곳에서 읽으세요. 픽셀 자체가 질문인 경우(레이아웃, 렌더링, 시각적 회귀 테스트 등)에만 스크린샷을 찍으세요.
- 채팅창에 이미지를 붙여넣는 것은 당신이 할 수 있는 가장 비용이 많이 드는 작업입니다. 이는 캐시 접두사(cache prefix)를 무효화합니다. 570k 컨텍스트 상황에서 이미지 한 번의 붙여넣기는 가중치 적용 시 약 737k에 달합니다. 이미지는 컨텍스트가 작을 때(세션 시작 시) 붙여넣고, 500k 상황에서는 붙여넣지 마세요.
/compact를 선제적으로 사용하세요. 자동 컴팩트(auto-compact)를 기다리지 마세요. 1M 윈도우에서는 영원히 실행되지 않을 수도 있습니다. 저는 이를 통해 626k에서 62.5k로 줄였으며, 이는 이후 모든 호출 비용을 10배 절감하는 결과를 가져왔습니다.
누적 사용량이 아닌 컨텍스트 크기 (context size)를 주시하세요. 누적 백분율 (%)은 당신이 이미 끝장났음을 알려줄 뿐입니다. 컨텍스트 크기는 현재의 소모율 (burn rate)을 알려줍니다. 경험적으로 측정했을 때, 가장 최악의 구간은 (계정 UI 기준) 5시간의 창(window) 중 약 46%를 소모한 47개의 요청이었으며, 이는 약 600k 컨텍스트에서 도구 호출 (tool call)당 창의 약 1%를 사용한 셈입니다. 62k로 압축한 후에는 호출당 약 0.1%가 되었습니다. 동일한 작업을 수행하면서도 실행 가능 시간 (runway)은 10배 늘어난 것입니다.
- 독립적인 도구 호출 (tool calls)을 하나의 메시지로 배치하세요. 최악의 구간 동안 저는 21분 동안 연속해서 약 27초마다 한 번씩 요청을 보냈습니다. 그중 상당수는 단일 메시지에 포함될 수 있었던 순차적인 탭 (taps)들이었습니다. 제가 앞서 지적한 함의는 모델 (model)과 노력 값 (effortValue)이 캐시 키 (cache key)에 포함되어 있다는 점입니다. 이는 세션 중간에 모델이나 추론 노력 (reasoning effort)을 동적으로 낮춤으로써 "할당량 (quota)을 보호"하려는 모든 도구가 전체 캐시 접두사 (cache prefix)를 무효화하고, 1.25배의 비용으로 전체를 다시 쓰게 만든다는 것을 의미합니다. 570k 컨텍스트에서 이러한 전환 한 번은 캐시 TTL (Time To Live)에 따라 약 713k에서 1.14M의 가중 입력 상당 (weighted input-equivalents) 비용을 발생시킵니다. 이것이 손익분기점을 넘으려면 엄청난 양의 후속 작업을 절약해야만 합니다. 대부분의 세션에서는 절약하는 것보다 더 많은 비용이 들면서 동시에 더 나쁜 출력을 제공하게 될 것입니다. 만약 할당량 관리 플러그인을 사용 중이라면, 이것을 수행하는지 확인하십시오. 이는 제가 메가스레드 (megathread)에서 반복적으로 목격한 패턴을 설명해 줄 수도 있습니다. 누군가 "할당량을 아끼기 위해" 세션 중간에 모델을 전환하면, 어시스턴트가 두 문장 정도 내뱉자마자 한도가 즉시 다시 초과되는 현상 말입니다. 높은 컨텍스트 상태에서의 모델 전환은 당신이 할 수 있는 가장 비용이 많이 드는 단일 작업 중 하나이며, 외부에서 보기에는 정확히 "계량기가 고장 난 것"처럼 보입니다. (범위를 명확히 하자면: 세션 사이에서 모델을 전환하거나, 새로운 세션 시작부터 더 저렴한 모델을 기본값으로 사용하는 것은 괜찮으며 실제로 할당량을 절약합니다.)
함정은 특히 대규모 컨텍스트 (context)가 쌓인 상태에서 세션 중간에 모델을 전환하는 것입니다.) 동일한 메커니즘이 최근의 또 다른 메가스레드 (megathread) 보고 내용을 설명할 가능성이 높습니다. 즉, 오케스트레이터 (orchestrator)가 몇 시간 동안 서브에이전트 (subagents)를 저렴하게 실행하다가, 리셋 후 해당 서브에이전트들을 다시 활성화하라는 요청을 받자 5시간 분량의 제한치를 단 20분 만에 모두 소모해 버린 사례입니다. 각 서브에이전트는 자신만의 캐시 접두사 (cache prefix)를 가지므로, 각각 대규모 컨텍스트를 보유한 N개의 에이전트를 가동(또는 재활성화)하는 것은 비싼 요율로 N번의 별도 전체 컨텍스트 캐시 쓰기 (cache writes)를 수행하는 것과 같습니다. 제 세션에서는 서브에이전트를 측정하지 않았으므로, 이는 입증된 사실이라기보다 메커니즘상 일관된 추론으로 취급하십시오. 하지만 각각 수십만 토큰의 컨텍스트를 가진 4개의 에이전트를 재활성화한다면, 단 몇 분 만에 수백만 개의 가중치 적용 토큰 (weighted tokens)이 소모될 것이며, 이는 해당 사용자가 설명한 상황과 정확히 일치합니다. 제가 불공정하게 과금된 것일까요? 제 경우에는 아닙니다. 저는 요청당 50만 토큰에 달하는 스크린샷 기반 워크플로우 (workflow)를 실행하고 있었고, 57만 토큰의 컨텍스트에 이미지를 붙여넣고 있었습니다. 미터기 (meter)는 제가 수행한 작업을 정확히 측정하고 있었던 것입니다. 귀하의 사례는 진정으로 다를 수 있습니다. 메가스레드에는 (유령 소비...
AI 자동 생성 콘텐츠
본 콘텐츠는 r/ClaudeAI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기