EP.2에서 hooks로 Claude가 읽는 것을 줄였다. 테스트 결과는 실패만 출력하고, 빌드 로그는 에러만 발라냈다.
얼마나 줄어드는지 측정하기로 했다.
측정부터
Claude Code는 모든 대화를 JSONL 파일로 남긴다. ~/.claude/projects/ 아래에 프로젝트별로 쌓이는데, 각 항목에 토큰 사용량이 들어 있다. 이걸 읽어서 비용을 계산하는 스크립트(analyze-tokens.js)를 만들었다. 날짜별 세션 파일과 교차 비교해서 턴 수, cache_read/write/output을 각각 집계하고 비용으로 환산한다.
만드는 과정에서 버그를 하나 잡았다. 세션 기록 hook이 e.usage를 참조하고 있었는데, JSONL 구조는 e.message.usage였다. 세션 파일에 토큰이 기록 안 되고 있었던 거다. 분석기는 JSONL을 직접 읽도록 우회했다.
아래는 3월 27일 하루의 비용이다.
총 비용의 58%는 읽기
| 항목 | 비용 | 비중 |
|---|---|---|
| cache_read | $19.22 | 58% |
| cache_write | ~$9.62 | 29% |
| output | ~$4.33 | 13% |
| 합계 | $33.17 |
cache_read가 58%다. Claude가 생성한 비용의 절반 이상이 “읽기"라는 뜻이다. EP.1에서 “Claude가 소모하는 토큰의 대부분은 추론이 아니라 읽기"라고 썼는데, 숫자로 보니까 확실하다.
뭘 읽고 있는 거야
Claude가 매 turn 읽는 것들을 추적했다.
| 항목 | 로드 시점 | 규모 |
|---|---|---|
| 프로젝트 CLAUDE.md | 매 turn | 155~214줄 |
| 루트 CLAUDE.md | 매 turn | 10줄 |
| MEMORY.md | 매 turn | 23줄 이내 |
| 대화 히스토리 | 매 turn | 누적 |
의외의 발견이 있었다. SKILL.md(469줄)와 에이전트 문서(1,233줄)는 매 turn 로드되지 않는다. 스킬이 호출될 때만, 에이전트가 실행될 때만 올라간다. 이미 lazy loading이다.
매 turn 고정으로 올라가는 건 CLAUDE.md와 MEMORY.md뿐이다.
CLAUDE.md를 뜯어서 rules/로
CLAUDE.md가 155~214줄이라고 해서 전부 매 turn 필요한 건 아니다. 산출물 경로 테이블은 산출물을 쓸 때만 필요하고, 게임 도메인 규칙은 게임 관련 작업을 할 때만 필요하다.
Claude Code에는 .claude/rules/ 디렉토리가 있다. 여기에 파일을 만들고 paths: frontmatter를 붙이면, 해당 경로에 접근할 때만 로드된다.
---
paths: tasks/**
---
산출물 경로 테이블, 공통 헤더 블록, 역할별 규칙...
tasks/ 폴더를 건드릴 때만 이 규칙이 올라온다. 일반 대화에서는 올라오지 않는다.
한 프로젝트에서 먼저 시범 적용했다. 산출물 관련 섹션 45줄을 artifact-rules.md로 분리하고, CLAUDE.md에서 해당 부분을 지웠다. 동작했다. 나머지에도 일괄 적용했다.
| 프로젝트 | CLAUDE.md (전→후) | 절감률 | 분리된 파일 |
|---|---|---|---|
| A (쇼핑몰) | 155줄 → 96줄 | 38% | artifact-rules.md |
| B (교육 플랫폼) | 207줄 → 140줄 | 32% | artifact-rules.md, edu-rules.md |
| C (게임 플랫폼) | 214줄 → 114줄 | 47% | artifact-rules.md, game-domain.md |
C가 47%나 줄었다. 게임 플랫폼이라 도메인 규칙(46줄)이 매 turn 올라가고 있었는데, 이제 게임 관련 파일을 건드릴 때만 올라간다.
그런데 말입니다
수치가 나쁘지 않다. 32~47% 경량화. 보기엔 좋다.
하지만 솔직히 말하면, 이건 “CLAUDE.md 155214줄 중에서 3247%“다. turn당 수백 토큰 수준의 절감이다. cache_read $19.22의 대부분은 CLAUDE.md 200줄이 아니라 대화 히스토리 누적이 원인이다. 한 세션에서 20턴을 돌리면, 20턴째에는 앞의 19턴 전체가 cache_read로 올라간다.
하루 비용의 절반 이상을 차지한 프로젝트가 있었다. 다른 프로젝트보다 3배 이상 비쌌다. 이유는 단순하다. 한 세션에서 17턴을 돌렸다. 후반부 턴의 cache_read가 급증한 거다.
CLAUDE.md를 아무리 다이어트시켜도, 대화 히스토리가 쌓이는 구조를 건드리지 않으면 cache_read의 대부분은 그대로다. EP.1에서 에이전트 수를 줄이고, EP.2에서 hooks로 출력을 줄이고, EP.3에서 고정 문서를 줄였다. 전부 의미 있는 작업이지만, 진짜 큰 바가지는 여전히 새고 있다.
끊을 것인가, 이어갈 것인가 그것이 문제로다
20턴을 넘기면 새 세션을 시작하는 게 cache_read 관점에서는 효율적이다. 하지만 새 세션을 시작하면 cache_write가 다시 발생한다. CLAUDE.md, MEMORY.md, 시스템 프롬프트를 처음부터 다시 쓴다.
cache_read를 줄이려고 세션을 끊으면 cache_write가 늘어난다. cache_write를 줄이려고 세션을 이어가면 cache_read가 늘어난다. 어느 쪽이든 비용이 든다.
정답은 아직 모른다. 20턴 정도가 손익분기점이라는 감은 있지만, 작업 유형에 따라 다르다. 대화가 많은 설계 작업은 빨리 끊는 게 낫고, 연속 코딩은 이어가는 게 낫다.
이게 EP.3의 진짜 발견이다. 줄일 수 있는 건 줄였다. 남은 건 “어떻게 쓸 것인가"의 영역이다.
이 시리즈의 다른 글
참고