에이전트 검색은 비용 곡선의 문제다: Claude Code가 RAG를 사용하지 않는 이유
요약
Claude Code가 코드 검색에 RAG 대신 grep 방식을 사용하는 근본적인 이유를 비용 곡선 관점에서 분석합니다. 인덱스 기반 검색의 높은 유지보수 비용과 대비하여, LLM 툴 루프 방식의 경제적 효율성과 정확성을 설명합니다.
핵심 포인트
- RAG의 높은 구축 및 유지보수 비용 문제
- 코드 검색에서 정밀도(Precision)의 중요성
- LLM 툴 루프 방식의 선형적 비용 구조
- Claude Code의 내부 동작 원리 분석
최근 유행하는 인터뷰 질문이 하나 있습니다: "왜 Claude Code는 코드를 검색하기 위해 RAG를 사용하지 않고 grep을 사용할까요?"
흔히 알려진 답변은 다음과 같습니다: 청킹(chunking)은 코드 구조를 깨뜨리고, 벡터(vectors)는 코드가 정확성을 요구할 때 근사치만을 제공하며, 인덱스(indexes)는 최신 상태를 유지하기 어렵고, 콜드 스타트(cold-start)는 느리며, 검색(retrieval)은 블랙박스라는 것입니다. 이 다섯 가지 모두 실제적인 문제입니다. 하지만 이 중 어느 것도 진짜 이유는 아닙니다.
이것들은 증상일 뿐입니다. 진짜 이유는 RAG보다 오래되었고, LLM보다 오래되었으며, _검색(retrieval)_이라는 용어보다 더 오래되었습니다. 그것은 바로 **비용 곡선(cost curve)**입니다.
요약(tl;dr) — 인덱스 기반 검색(Index-based retrieval)은 높은 구축 비용과 더불어, 코드 변경(churn) × 인덱스 복잡성에 따른 비선형적(nonlinear) 유지보수 비용을 지불해야 합니다. LLM 도구 루프 검색(LLM tool-loop retrieval)은 초기 비용이 들지 않으며, LLM이 실제로 실행하는 쿼리에 대해서는 프로젝트 규모와 거의 무관한 쿼리당 비용을 지불합니다. 대부분의 중소규모 리포지토리(repos)에서는 이 두 방식의 교차점(crossover)에 도달하지 않습니다. "Anthropic이 모델을 신뢰한다"는 프레임은 낭만적이지만, 실제 답은 더 냉혹합니다. 구축 비용은 0이고, 쿼리당 비용은 인덱스 드리프트(index drift)보다 빠르게 상쇄되므로, 수학적으로는 grep이 정답이라는 것입니다.
또한 정밀도(precision) 축이 있는데, 대부분의 엔지니어는 비용보다 이를 더 중요하게 생각합니다. 벡터 RAG(Vector RAG)는 설계상 근사치를 제공합니다.
getUserById가getUserByEmail과 함께 반환되는 이유는 그것들이 의미론적으로 인접하기 때문입니다. 코드는 대개 정확한(exact) 결과를 원하며, grep은 이를 무료로 제공합니다. 심볼 그래프(Symbol-graph) 인덱스(Sourcegraph, Kythe, LSP)는 정밀도를 우선시하지만, LLM의 동반자가 되지는 못했습니다 — 아래에서 다룹니다.이 내용은 파일:라인 인용 기능이 포함된 Claude Code의 공개적으로 유출된 빌드 스냅샷을 바탕으로 검증되었습니다. 핵심은 다음과 같습니다: Explore 서브 에이전트의 "3개 이상의 쿼리가 필요할 때 사용하라"는 규칙은 기능 플래그(
tengu_amber_stoat) 뒤에 숨겨져 있으며, 병렬 아키텍처(Fork)와 A/B 테스트 중입니다. 정석적인 답변은 조건부입니다. _그것_이 바로 당신에게 합격 통보를 안겨줄 답변입니다.
중요한 프레임: 시간에 따른 총 비용
어떤 검색 시스템을 선택하든, 당신은 서로 다른 일정에 따라 세 가지 항목에 대해 비용을 지불하게 됩니다:
- Build cost (구축 비용) — 검색을 빠르게 만드는 구조를 조립하기 위한 일회성 작업입니다. 인덱스(Index)의 경우, 이는 청킹(Chunking) + 임베딩(Embedding) + 삽입(Insert) 과정입니다. 툴 루프(Tool-loops)의 경우, 이 비용은 0입니다.
- Maintain cost (유지 비용) — 기초 데이터가 변경됨에 따라 구조를 정확하게 유지하기 위한 지속적인 작업입니다. 인덱스의 경우, 이는 무효화(Invalidation), 재인덱싱(Reindex), 드리프트 조정(Drift reconciliation)입니다. 툴 루프의 경우
- 청킹 (Chunking)은 구조를 파괴합니다. 여러 청크에 걸쳐 나뉜 함수는
if/else구문의 양쪽 절단을 모두 잃게 되며, 호출 그래프 (call-graph) 관계가 청크 사이에서 파편화됩니다. AST 인식 (AST-aware) 청커가 존재하지만, 이는 개선된 수준일 뿐 해결된 것은 아닙니다. - 벡터 (Vectors)는 근사치입니다.
getUserById가getUserByEmail및getUserByName과 함께 반환되는 이유는 이들이 의미론적으로 인접해 있기 때문입니다. 정확한 심볼 (symbol) 검색은 이를 아주 쉽게 능가합니다. 중요한 차이점: 이는 특히 **벡터 RAG (vector RAG)**에 해당되는 이야기입니다. 심볼 그래프 인덱스 (Symbol-graph indexes) — Sourcegraph, Kythe, Glean, LSP 기반 코드 검색 — 는 다른 범주입니다. 이들은 청킹된 벡터가 아니라 함수/클래스/참조를 기준으로 인덱싱합니다. 이들은 정확한 답을 제공하며
왜일까요? 코드 검색의 기준점(baseline) — 즉, LLM이 개입하는 깨끗한 파일 시스템 상의 grep — 이 이미 충분히 잘 작동하기 때문입니다. 여기에 인덱스(index)를 추가하는 것은 인덱스 자체가 근본 원인인 증상들을 해결하기 위해 엔지니어링 비용을 지불하는 것과 같습니다. 인덱스를 제거하면 고통도 사라집니다. 남은 비용은 쿼리당 LLM 왕복(round-trips) 비용이며, 비용 곡선(cost-curve) 프레임워크에 따르면 이는 교차점(crossover) 아래에서는 수용 가능한 수준입니다.
그것이 바로 grep을 사용하는 이유입니다. 그 외의 모든 것은 그 결정 위에 쌓이는 엔지니어링 세부 사항일 뿐입니다.
그렇다면 왜 심볼 그래프(Symbol-Graph) 또한 LLM 동반자가 되지 못했는가?
만약 심볼 그래프 인덱스가 정밀도를 우선시하고, 언어를 인식하며, FAANG 규모에서 검증되었다면, 자연스러운 질문은 이것입니다. 왜 그것들이 LLM 코딩 에이전트의 기본 동반자가 되지 못했는가? 왜 MCP 기반의 LSP가 아니라 grep인가?
그 답은 벡터 RAG(vector-RAG)의 답변과 같은 형태를 띱니다. 즉, 기능 비교표에는 나타나지 않는 부분에서 발생하는 높은 마찰(friction) 때문입니다. 다만 구체적인 마찰의 종류가 다를 뿐입니다.
- 빌드 비용이 다른 방식으로 높습니다. 심볼 그래프 인덱스는 심볼(symbols)을 해석하기 위해 프로젝트를 컴파일(또는 준컴파일)해야 합니다. Rust, C++, 대규모 TypeScript 또는 Java 코드베이스의 경우, 콜드 스타트(cold start) 시 수 분에서 수십 분이 소요됩니다. "Claude Code를 열고 바로 작업을 시작한다"는 경험은 그런 통행료를 지불할 수 없습니다.
- 언어에 종속적이며 이식성이 낮습니다. LSP는 언어당 하나의 서버가 필요합니다. Tree-sitter 지원이 도움이 되긴 하지만 균일하지는 않습니다. grep 기반의 에이전트는 설정 없이 어떤 언어의 어떤 텍스트에서도 작동하지만, 심볼 그래프 기반의 에이전트는 프로젝트의 언어 서버(language-server) 매트릭스를 그대로 물려받아야 합니다.
- LLM의 추론 방식과 API/포맷이 일치하지 않습니다. LSP는 깊게 중첩된 JSON(위치, 범위, 문서 계층 구조)을 반환하지만, grep은
file:line: content를 반환합니다. 후자는 거의 문자 그대로 LLM의 네이티브 방언(native dialect)에 가깝고, 전자는 적응 과정이 필요합니다. 이러한 변환 비용(translation tax)은 실제로 존재합니다. - 커버리지가 보기보다 좁습니다. 심볼 그래프는 코드를 구조로서 모델링합니다.
설정 파일(config files), 주석(comments), 문자열(strings), 생성된 코드(generated code), 마크다운(markdown), 환경 파일(.env files), 셸 스크립트(shell scripts), README 등은 실제 코딩 세션에서 일급 객체(first-class) 컨텍스트입니다. 하지만 심볼 그래프는 이를 놓칩니다. Grep은 텍스트인 것이라면 무엇이든 커버합니다.
-
승리는 의도(intent) 질문이 아닌 구조(structure) 질문을 위한 것입니다. "
getUserById가 어디에 정의되어 있나요?"라는 질문에는 심볼 그래프(symbol-graph)가 정확합니다. "로그인 흐름이 어떻게 작동하나요?"라는 질문에는 다시 grep + 읽기(read)로 돌아가야 합니다. 실제 코딩 업무에는 이 두 가지 종류가 모두 존재합니다. 한 종류만 해결하는 인프라를 구축하는 것은 절반의 정답을 위해 높은 고정 비용을 지불하는 것과 같습니다. -
그 아래에서 제약 조건이 역전되었습니다. 심볼 그래프는 제약 조건이 *인간의 주의력 대역폭(human attention bandwidth)*이었던 세상을 위해 설계되었습니다. 즉, 개발자에게 읽을 수 있는 하나의 정확한 답변을 제공하는 것입니다. LLM은 그러한 제약이 없습니다. LLM은 30개의 grep 검색 결과를 저렴하게 읽고 그 사이의 관계를 추론할 수 있습니다. 병목 현상은 "검색의 정밀도(precision of retrieval)"에서 "검색 결과를 읽는 모델의 유창성(fluency of the model reading the retrieval)"으로 이동했습니다. 심볼 그래프는 더 이상 비용이 많이 들지 않는 부분을 최적화하고 있는 셈입니다.
한 줄 요약: 심볼 그래프는 인간용 IDE를 위해 구축된 정밀 도구입니다. LLM은 인간용 IDE가 아닙니다. 이들의 검색 병목 현상은 다릅니다. 이들은 하나의 비싼 정밀 호출보다 여러 번의 저렴한 반복을 선호합니다. 한 세션 동안 30번의 grep을 수행할 수 있는 에이전트에게 심볼 그래프를 설치하는 것은, 이미 운전을 할 줄 아는 사람에게 두 번째 운전사를 고용하는 것과 대략 비슷합니다.
이것이 바로 몇 안 되는 기존의 LLM ↔ 심볼 그래프 통합 사례들(LSP를 통한 Cursor의 @symbol 참조, Sourcegraph Cody, LSP 백엔드를 사용하는 Codeium)이 해당 제품들에서 검색의 중추(backbone)가 아닌 *부가적인 편의 기능(additive niceties)*인 이유이기도 합니다. 중추는 여전히 Claude Code와 마찬가지로 텍스트에 대한 grep입니다.
세 가지 기본 요소(Primitives) 검토
아래의 소스 경로는 분석 목적으로 제가 디스크에 보유하고 있는, 공개적으로 유포된 Claude Code의 비공개 빌드 스냅샷에서 가져온 것입니다. API와 정확한 줄 번호는 달라질 수 있습니다. 아래의 설계 선택 사항들은 제가 검토한 스냅샷에서 안정적이었으며, 현재 공개된 Claude Code 릴리스에서 관찰된 런타임 동작과 일치합니다.
Grep — 구조화된 출력과 "shell out 금지" 강제 조항이 포함된 ripgrep
src/tools/GrepTool/prompt.ts:7-16에서 가져온 Grep 도구 설명은 짧고 명확합니다:
ripgrep을 기반으로 구축된 강력한 검색 도구
Usage:
...
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기