대규모 코드베이스가 AI 토큰 예산을 소모하는 이유와 이를 최적화하는 방법
요약
대규모 코드베이스에서 AI 모델이 컨텍스트 예산을 과도하게 소모하는 원인을 분석하고, 이를 최적화하기 위한 구조적 해결책을 제시합니다. CLAUDE.md, 규칙(Rules), 스킬(Skills)을 활용하여 암묵적 지식을 명시화함으로써 토큰 비용을 절감하는 방법을 다룹니다.
핵심 포인트
- 대규모 저장소의 모호성과 컨벤션 재유도가 토큰 소모의 주원인임
- CLAUDE.md를 통해 탐색적 파일 읽기를 줄이고 컨텍스트를 효율화 가능
- 금지 규칙(Prohibition rules) 설정으로 잘못된 코드 생성 및 수정 비용 방지
- 스킬(Skills) 기능을 활용해 필요한 시점에만 컨텍스트를 로드하여 예산 관리
대규모 코드베이스에서 컨텍스트 예산(context budget)이 어디로 소모되는지에 대한 기술적 분석과, 정확도를 높이면서 소모량을 줄이는 구조적 패턴에 대하여.
Hook: Claude의 도움을 받아 대규모 NestJS 모노레포(monorepo)에 새로운 기능을 추가합니다. 대화가 끝날 때쯤, 모델은 40개의 파일을 읽었고, 당신의 모듈 컨벤션(convention)을 두 번이나 다시 유도(re-derive)했으며, 여전히 서비스 배치(service placement)를 틀렸습니다. 당신은 수천 개의 토큰을 사용했습니다. Claude는 그중 대부분을 추측하는 데 사용했습니다. 이 가이드는 그 이유와 해결책을 설명합니다.
핵심 요약 (Key Takeaways)
- 컨텍스트 윈도우(Context windows)는 코드베이스 규모에 따라 확장되지 않습니다. 대규모 저장소(repo)는 더 많은 결정, 더 많은 컨벤션, 더 많은 모호성을 포함하며, 이 모든 것이 토큰 소모를 증가시킵니다.
- 가장 큰 토큰 소모 요인은 재유도(re-derivation)와 불확실성입니다. Claude는 아키텍처를 모를 때 더 많은 파일을 읽고, 컨벤션을 모를 때 더 많은 텍스트를 생성합니다.
CLAUDE.md는 가장 효율적인 토큰 절약 수단입니다. 300줄 정도의 브리핑 파일은 세션당 수백 줄에 달하는 탐색적 파일 읽기(exploratory file reads)를 대체할 수 있습니다.- 규칙(Rules)은 재생성을 방지함으로써 토큰을 절약합니다. 수정이 필요한 잘못된 패턴은 생성할 때 한 번, 수정할 때 한 번, 총 두 번의 비용이 발생합니다. 금지 규칙(prohibition rule)은 이를 완전히 방지합니다.
- 스킬(Skills)은 필요할 때만 컨텍스트를 로드합니다. 스킬에 포함된 템플릿(templates)과 참조(references)는 매 세션마다 로드되는 것이 아니라, 호출될 때만 컨텍스트 윈도우에 들어옵니다.
빠른 답변 (Quick Answer)
대규모 저장소는 AI 토큰을 빠르게 소모합니다. 왜냐하면 Claude가 숙련된 팀원이라면 이미 알고 있을 내용들, 즉 어떤 서비스가 무엇을 소유하는지, 에러 핸들링(error-handling) 패턴은 무엇인지, 응답(responses)이 어떤 형태로 구성되는지, 무엇이 의도적으로 제거되었는지 등을 추론하기 위해 파일을 읽어야 하기 때문입니다. 모든 탐색적 읽기, 모든 잘못된 서비스 추측, 컨벤션을 위반하여 수정이 필요한 모든 패턴은 낭비되는 컨텍스트입니다. 해결책은 이러한 암묵적 지식(tribal knowledge)을 CLAUDE.md(매 세션 로드되는 브리핑), 규칙(잘못된 출력을 방지하는 금지 사항), 그리고 스킬(템플릿을 활용한 온디맨드 워크플로우)로 외부화하는 것입니다. 한 번만 설정하면 모든 기능 구현 시 반복 적용됩니다.
문제점: 컨텍스트 예산은 유한한 자원입니다
Claude가 읽거나 쓰는 모든 토큰은 소모되는 예산입니다. 컨텍스트 윈도우 (Context window)는 파일이나 줄(line) 단위가 아닌 토큰 단위로 측정되며, 모든 세션은 동일한 상한선에서 시작합니다. 작은 프로젝트에서는 이 예산이 넉넉할 수 있습니다. 하지만 여러 서비스, 수십 개의 모듈, 수백 개의 DTO, 그리고 횡단 관심사(cross-cutting) 규칙이 존재하는 대규모 모노레포 (monorepo)에서는 예산이 빠르게 증발합니다.
예산은 다음 두 곳에서 소모됩니다:
- 입력 토큰 (Input tokens) — Claude가 읽는 모든 것: 사용자의 프롬프트 (prompt), 열어보는 파일, 로드하는 규칙 및 문서, 진행 중인 대화 기록.
- 출력 토큰 (Output tokens) — Claude가 생성하는 모든 것: 코드, 설명, 수정 사항, 재생성 (re-generation).
두 가지 모두 비용이 많이 듭니다. 하지만 대규모 저장소에서는 입력 측면에서 출혈이 시작됩니다. Claude가 자신이 모르는 부분을 보완하기 위해 공격적으로 읽어들이기 때문입니다.
토큰이 실제로 소모되는 곳
1. 탐색적 파일 읽기 (Exploratory File Reads)
Claude가 특정 요소가 어디에 있는지 모를 때, 이를 찾아 나섭니다. 잘 인덱싱된 코드베이스라면 적절한 모듈을 찾기 위해 34개의 파일을 읽는 것으로 충분할 수 있습니다. 하지만 서비스 경계가 모호한 대규모 모노레포에서는 행동에 확신을 갖기 전까지 1520개의 파일을 읽어야 할 수도 있습니다.
"예약 패키지에 사용자에게 보이는 상태(status)를 추가해줘"라는 요청에 대해, 설정되지 않은 일반적인 세션 흐름은 다음과 같습니다:
services/디렉토리 목록 읽기 — 방향 파악 (orientation)operator-service/src/modules/order/읽기 — 잘못된 서비스, 불일치 발견api-service/src/modules/order/읽기 — 경로 수정order.entity.ts읽기 — 데이터 구조(shape) 확인order.service.ts읽기 — 패턴 확인order.repository.ts읽기 — 쿼리 레이어 (query layer) 확인order.controller.ts읽기 — 응답 구조 (response shape) 확인order.dto.ts읽기 — DTO 컨벤션 (convention) 확인transform-response.helper.ts읽기 — 어댑터 패턴 (adapter pattern) 확인- 서비스 간 어댑터 사용을 검증하기 위해 3~4개의 파일을 추가로 읽음
단 한 줄의 출력물을 작성하기도 전에 약 10개의 파일을 읽는 셈입니다. 각 읽기 작업은 입력 토큰 (input tokens) 비용을 발생시킵니다. 대규모 NestJS 모노레포 (monorepo)에서 파일은 결코 작지 않습니다. 서비스 (services), 리포지토리 (repositories), 컨트롤러 (controllers)는 각각 통상적으로 200~400라인을 초과합니다.
설정된 세션에서의 동일한 작업: Claude는 CLAUDE.md를 읽고 서비스 경계 (service boundary)를 즉시 파악하며, 직접적으로 관련된 3개의 파일만 읽은 뒤 생성을 시작합니다. 탐색적인 읽기 (exploratory reads)는 전혀 발생하지 않습니다.
2. 컨벤션 재도출 (Convention Re-derivation)
Claude가 올바른 파일에 도달하더라도, 여전히 팀의 컨벤션 (conventions)을 추론해야 합니다. 명시적인 문서가 없다면, 패턴을 재구성하기 위해 예시들을 읽게 됩니다:
- 응답이 어떻게 구성되는지 배우기 위해 기존 컨트롤러 (controllers)를 읽음
- 어떤 로거 (logger)를 사용하는지 찾기 위해 기존 서비스 (services)를 읽음
- 권한 시스템 (permission system)을 이해하기 위해 기존 가드 (guards)를 읽음
- 검증 패턴 (validation patterns)을 추론하기 위해 여러 개의 DTO를 읽음
이는 예시를 통한 패턴 매칭 (pattern-matching)입니다. 효과는 있지만, 매 세션마다, 매 엔지니어마다, 무기한으로 토큰 비용이 발생합니다. 컨벤션은 변하지 않지만, 재도출 과정은 반복됩니다.
세션당 컨벤션 재도출 비용 (대규모 NestJS 모노레포 기준 대략적인 추정치):
| Claude가 재도출해야 하는 컨벤션 | 통상적으로 읽는 파일 수 | 추정 토큰 비용 |
|---|---|---|
| 서비스 소유권 (어느 곳에 속하는지) | 3–5개의 엔트리 포인트 (entry points) | ~2,000–4,000 |
| ... |
이는 프롬프트 (prompt)를 입력하기 전, 출력을 내보내기 전, 그리고 작업 특정적인 읽기 (task-specific reads)를 수행하기 전의 비용입니다. 이는 오버헤드 (overhead)이며, 팀이 이미 보유하고 있는 지식을 순수하게 다시 도출하는 과정에서 발생하는 비용입니다.
3. 잘못된 서비스 배치 및 수정 (Wrong-Service Placement and Corrections)
코드베이스에 도메인이 중첩되는 여러 서비스가 있는 경우 — 예를 들어, 운영자 서비스(정의)와 소비자 API(소비자에게 보이는 메타데이터) 양쪽에 모두 존재하는 package 엔티티 (entity) 같은 경우 — Claude는 추측을 하게 됩니다. 때로는 맞히기도 하지만, 그렇지 못한 경우가 많습니다.
잘못된 서비스 추측은 세 가지 방식으로 토큰을 소모합니다:
- 잘못된 서비스에서의 탐색 (Exploration in the wrong service) — 변경 사항이 적용되지 않을 서비스의 모듈 구조, 엔티티 (entities), 그리고 서비스 (services)를 읽는 행위
- 잘못된 출력 생성 (Generating the wrong output) — 잘못된 서비스에서 코드를 생성하는 행위
- 수정 (Correction) — 사용자가 오류를 지적하면, Claude가 다시 읽고, 다시 추론하고, 다시 생성하는 행위
실제로, 단 한 번의 잘못된 서비스 추측이 처음부터 올바른 구현을 수행하는 것보다 더 많은 토큰을 소모할 수 있습니다.
4. 패턴 위반 및 재생성 (Pattern Violations and Regeneration)
명시적인 금지 사항이 없다면, Claude는 학습된 기본값 (training defaults)을 사용합니다. 이러한 기본값은 합리적이지만, 귀하의 팀 컨벤션 (conventions)과는 일치하지 않을 것입니다. NestJS 프로젝트에서 흔히 발생하는 불일치는 다음과 같습니다:
- 구조화된 로거 (structured logger) 대신
console.log사용 PermissionCodes대신if (user.roles.includes('admin'))사용src/별칭 (alias) 대신 상대 경로../../임포트 (imports) 사용ErrorMessages상수 (constants) 대신 하드코딩된 에러 문자열 사용- 레포지토리 (repository)를 거치지 않고 서비스 내에서 직접적인 DB 쿼리 (raw DB queries) 수행
각 위반 사항은 토큰을 두 번 소모합니다. 한 번은 잘못된 패턴을 생성할 때, 또 한 번은 이를 수정할 때입니다. 세 번의 수정이 필요한 단 한 번의 코드 생성은 해당 블록의 출력 토큰 비용을 사실상 4배로 증가시킵니다.
5. 대화 기록 누적 (Conversation History Accumulation)
세션이 길어짐에 따라, 진행 중인 대화 기록 (conversation history)이 이후의 모든 호출에 포함됩니다. 긴 디버깅(debugging) 또는 구현 세션에서는 다음과 같은 현상이 발생합니다:
- 초기에 읽은 파일들이 더 이상 관련이 없을 때도 컨텍스트 (context)에 남아 있음
- 수정 및 재생성 작업이 기록에 추가됨
- 막다른 길(dead ends)에 대한 Claude의 추론 내용이 누적됨
대규모 코드베이스에서 20번의 대화가 오가는 세션은, 대화 기록만으로도 짧은 세션의 토큰 제한 (token limit)에 근접할 수 있습니다.
해결책: 지식을 외부화하고, 다시 유도하지 마세요 (Externalize Knowledge, Don't Re-Derive It)
과도한 토큰 소비의 근본적인 원인은 모든 경우에 동일합니다. 바로 Claude가 여러분의 팀이 이미 알고 있는 내용을 학습하는 데 토큰을 소모하고 있다는 점입니다. 해결책은 매 세션마다 소스 파일로부터 지식을 다시 유도(re-deriving)하는 대신, Claude가 효율적으로 읽을 수 있는 형태로 해당 지식을 사전에 제공하는 것입니다.
다음 세 가지 메커니즘이 이를 수행하며, 각 메커니즘은 서로 다른 유형의 지식에 적합합니다:
| 메커니즘 | 인코딩하는 내용 | Claude가 읽는 시점 | 토큰 영향 |
|---|---|---|---|
CLAUDE.md | 아키텍처 (Architecture), 소유권 (Ownership), 컨벤션 (Conventions) | 매 세션 시작 시 자동으로 로드됨 | 탐색적 읽기 (Exploratory reads)를 대체함 |
| ... |
CLAUDE.md: 탐색을 선언으로 대체하기
CLAUDE.md는 토큰 소비를 줄이기 위한 가장 강력한 레버리지 도구입니다. 이 파일은 모든 파일 읽기가 시작되기 전, 매 세션의 시작 단계에서 로드되며, 포함된 정보에 대해서는 탐색 단계(exploratory phase)를 완전히 대체합니다.
핵심 통찰은 **완전성보다 구체성 (Specificity over completeness)**입니다. NestJS의 모든 것을 설명하느라 비대해진 CLAUDE.md보다는, Claude가 파일을 읽어서 알아내야 했을 정확한 질문들에 답해주는 간결한 파일이 더 낫습니다:
- 어떤 서비스가 어떤 종류의 데이터를 소유하고 있는가?
- 표준 응답 형태 (Standard response shape)는 어떻게 생겼는가?
- 서비스 간 통신 (Cross-service communication)은 어떻게 작동하는가?
- 프로젝트 특화 패턴 (로거, 에러, 권한 등)은 무엇인가?
리포지토리 패턴 (Repository pattern)이 무엇인지까지 설명하는 1,000줄짜리 파일보다, 위 질문들에 정확히 답하는 300줄짜리 CLAUDE.md가 더 많은 토큰을 절약해 줍니다.
포함해야 할 내용
서비스 라우팅 결정 테이블 (Service Routing Decision Table)
이것은 문서화할 수 있는 가장 효과적인 단 한 가지 요소입니다. Claude가 새로운 기능의 소유 서비스가 어디인지 파악하기 위해 10개의 파일을 읽는 대신, 단 하나의 테이블을 읽게 하십시오:
소유 서비스 구분 (Which Service Owns What)
- operator-service — 데이터가 _경험이 무엇인지(what an experience is)_를 설명하는 경우:
...
이 테이블을 컨텍스트에 포함하는 데는 약 150 토큰이 소모됩니다. 이는 탐색적인 서비스 읽기에 소모되는 2,0004,000 토큰을 대체합니다. **이는 매 세션마다 1025배의 수익률을 가져다줍니다.**
그렇지 않으면 다시 유도될 관습 (Conventions That Would Otherwise Be Re-Derived)
단일 파일을 읽는 것만으로 이미 명확하게 알 수 없는 내용만 문서화하세요:
## Claude가 하지 말아야 할 것
- `console.log` → `src/common/utils/logger`의 `logMessage()`를 사용하세요
...
다섯 가지 금지 사항. 약 100 토큰. 이는 Claude가 이러한 패턴을 추론하기 위해 수행할 모든 파일 읽기를 대체하며, 패턴을 잘못 파악했을 때 발생하는 재생성 비용을 제거합니다.
응답 형태 (Response Shape)
산문 형태의 설명이 아닌, 정확한 형태를 포함하세요:
## 표준 응답 형태
컨트롤러(Controllers)는 `{ data, meta }`를 반환합니다. TransformInterceptor가 자동으로 래핑합니다:
...
이것이 없다면 Claude는 패턴을 추론하기 위해 2~3개의 컨트롤러를 읽어야 합니다. 이것이 있다면 Claude는 즉시 패턴을 파악하고, 처음부터 올바르게 수행합니다.
제외해야 할 항목
CLAUDE.md의 토큰 효율성은 무엇을 넣느냐만큼 무엇을 빼느냐에 달려 있습니다:
- 프레임워크 문서 (Framework documentation) — Claude는 NestJS 가드(guard)가 무엇인지 이미 알고 있습니다. 프로젝트에 특화된 가드 설정만 문서화하세요.
- 파일로부터 유도 가능한 코드 (Code that's derivable from files) — Claude가 파일 하나를 읽고 패턴을 학습할 수 있다면,
CLAUDE.md에 반복해서 적지 마세요. - 안정적이고 명확한 관습 (Stable, obvious conventions) — TypeScript 사용, async/await 사용, 모듈 패턴 준수 등. 이러한 것들은 기본값이므로 토큰을 소모하지만 추가적인 정보를 제공하지 않습니다.
- 작업 특정 템플릿 (Task-specific templates) — 템플릿은
CLAUDE.md가 아닌 스킬(skills)에 속해야 합니다. 템플릿은 매 세션이 아니라 필요할 때만 컨텍스트에 포함되어야 합니다.
목표: 300라인 미만. 만약 CLAUDE.md가 더 길다면, 유도 가능한 내용이 있는지 감사(audit)하세요. 파일 읽기를 방지하거나 잘못된 패턴을 막아주지 못하는 모든 라인은 삭제해야 할 라인입니다.
규칙: 근본 원인에서 재생성 방지
수정 후에도 다시 생성되는 잘못된 패턴은 처음부터 올바르게 수행했을 때보다 두 배의 토큰 비용이 발생합니다. 규칙은 잘못된 패턴 생성을 불가능하게 만듦으로써 이 비용을 제로(0)로 줄여줍니다.
규칙은 .claude/rules/ 디렉토리에 마크다운 (Markdown) 파일로 저장됩니다. 규칙은 '선호 사항'이 아닌 '금지 사항'의 형태로 작성되어야 합니다. 왜냐하면 "루프 안에서 DB를 절대 호출하지 마세요"는 강제되는 반면, "벌크 쿼리 (bulk queries)를 선호하세요"는 압박이 가해지는 상황에서 무시될 수 있는 제안에 불과하기 때문입니다.
규칙 (Rule) vs 수정 (Correction)의 토큰 비용
N+1 쿼리 상황을 가정해 보겠습니다. 규칙이 없다면 다음과 같은 일이 발생합니다:
- Claude가 루프 내부에 DB 호출이 포함된 코드를 생성합니다 (출력 토큰 (output tokens))
- 사용자가 생성된 코드를 읽고 N+1 문제를 식별합니다 (사용자의 시간)
- Claude에게 해당 문제를 설명합니다 (입력 토큰 (input tokens))
- Claude가 수정을 이해하기 위해 컨텍스트 (context)를 다시 읽습니다 (컨텍스트 재처리 (context re-processing))
- Claude가 올바른 벌크 쿼리 버전을 다시 생성합니다 (출력 토큰 (output tokens))
총합: 해당 블록에 대한 출력 토큰의 약 2배, 여기에 수정 교환을 위한 입력 토큰이 추가됩니다.
규칙이 있는 경우:
- 규칙이 컨텍스트에 포함되어 있음; Claude가 올바른 벌크 쿼리 패턴을 생성합니다 (출력 토큰 (output tokens))
총합: 1배의 출력 토큰.
규칙 자체를 세션에 포함하는 데는 약 200개의 토큰이 소모됩니다. 하지만 N+1 문제가 발생할 때마다 1배의 출력 토큰과 수정 오버헤드 (correction overhead)를 절약해 줍니다. 이러한 위반 사례가 3~4번 발생하는 세션이라면, 규칙 사용 비용을 수차례 상회하는 이득을 얻게 됩니다.
NestJS 프로젝트를 위한 고가치 규칙 (High-Value Rules)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기