도입: 200만 토큰이라는 숫자에 속지 마라

벤더들이 "컨텍스트 창 1M, 2M" 하고 광고하는 걸 보면, 이제 RAG 파이프라인에서 청킹 같은 거 신경 안 써도 되는 거 아닌가 싶은 생각이 든다. 그냥 문서 통째로 다 때려넣으면 알아서 잘 답해주겠지, 라고. 실무에서 한 번이라도 긴 세션을 굴려본 사람이라면 그게 착각이라는 걸 안다.

핵심은 간단하다. 광고되는 컨텍스트 창 크기 = 실제로 모델이 똑똑하게 쓸 수 있는 범위가 아니다. 원문(큰 컨텍스트 창을 신뢰하지 마라)에서 짚는 건 컨텍스트 창이 두 구간으로 나뉜다는 점이다. 모델이 예리하게 작동하는 "스마트 구간"과, 앞에서 한 지시를 슬슬 까먹기 시작하는 "둔한 구간". 그 경계가 대략 100k 토큰 부근이라는 얘기다.

이게 왜 지금 중요하냐면, 요즘 코딩 에이전트(Claude Code, Cursor 등)는 토큰을 미친 듯이 소모한다. 파일 몇 개 읽고, 긴 디버깅 세션 한 번 돌리고, 테스트 전체 한 번 실행하면 100k는 우습게 넘긴다. 즉 우리가 일상적으로 굴리는 작업이 자기도 모르게 "둔한 구간"에 진입한다는 거다.

핵심: 100k 임계점과 둔한 구간의 정체

스마트 구간 vs 둔한 구간

비유를 하나 들자면, 회의에 들어가서 처음 10분은 다들 집중해서 안건을 또렷이 기억한다. 그런데 두 시간짜리 회의가 끝나갈 무렵엔 "아 그래서 아까 그 결정이 뭐였죠?" 하는 사람이 나온다. LLM도 비슷하다. 컨텍스트 창 앞쪽 일부 청크는 또렷하게 처리하지만, 창을 채워나갈수록 주의력이 분산되면서 성능이 점진적으로 떨어진다.

원문에서 인용하는 RULER 벤치마크와 Chroma의 context rot 보고서가 이걸 데이터로 보여준다. 요지는 유효 컨텍스트(effective context)는 광고된 숫자의 일부에 불과하고, 창을 채울수록 성능이 떨어진다는 것. 단, 원문에서도 명시하듯 이 연구들은 2024~2025년 자료라 최신 Claude 모델에 그대로 적용된다고 보긴 어렵다. 모델 버전마다 어텐션 구조와 장기 컨텍스트 학습량이 다르기 때문이다. 그래서 "100k"라는 숫자도 절대값이라기보단 경험적 가이드라인으로 받아들이는 게 맞다.

왜 잊어버리는가 — Attention 관점

Transformer의 self-attention은 입력 토큰 전부에 대해 서로의 관련도를 계산한다. 토큰이 N개면 attention 계산은 대략 N² 스케일로 늘어난다. 문제는 토큰이 많아질수록 "지금 이 작업에 진짜 중요한 토큰"에 쏠려야 할 주의력이, 관련 없는 수많은 토큰들과 나눠 가지게 된다는 점이다.

원문 HN 댓글 중에 이걸 잘 짚은 게 있다. "컨텍스트 창 안에서 어떤 것이 자주 등장하면, 설령 틀린 것이어도 가중치가 생긴다." 즉 디버깅 세션에서 실패한 접근법이 컨텍스트에 계속 쌓이면, 모델이 그 실패한 접근을 자꾸 다시 시도하는 현상이 생긴다. 절대적인 컨텍스트 크기보다 창 안에 찌꺼기와 잘못된 방향 지시가 쌓여서 정작 중요한 내용을 덮어버리는 것이 더 큰 문제라는 지적이다. 이거 실무에서 정말 자주 겪는다.

실무 관점: 어디서 터지고, 어떻게 막는가

흔한 함정 1 — 코딩 에이전트가 둔한 구간에서 압축을 시작한다

Claude Code의 auto-compact가 대표적이다. 세션이 길어지면 기록을 요약하고 새로 시작하는 기능인데, 함정은 이미 둔한 구간에 들어간 다음에 작동한다는 점이다. 게다가 요약을 만드는 모델 자체가 이미 성능이 저하된 상태다. 멍청해진 모델이 만든 요약을 들고 다음 세션을 시작하니, 인계 품질이 떨어진다.

실제로 긴 세션에서 이런 메시지를 만나게 된다:

Context low (8% remaining) · Run /compact to compact & continue

이게 뜨기 전에 이미 작업 품질이 떨어지고 있었다는 게 핵심이다. 그리고 도구 호출이 실패하기 시작하면 이런 것도 본다(에이전트/하네스마다 메시지는 다르다):

Error: Input is too long for requested model.
prompt token count (215431) exceeds the maximum (200000)

이 에러를 만났다면 이미 한참 전부터 모델이 흐리멍덩해진 상태로 작업하고 있었다고 봐야 한다.

흔한 함정 2 — "메모리 시스템"이 오히려 모델을 멍청하게 만든다

원문 댓글에서 강하게 동의가 모인 지점이다. "모델에는 메모리가 있는 게 아니라 컨텍스트만 있다." 관련 없는 사실을 메모리랍시고 컨텍스트에 계속 밀어 넣으면, 정작 현재 문제에 쓸 컨텍스트가 줄어든다. 방해가 적을수록 결과가 좋아진다는 것.

한 댓글은 이런 웃픈 상황도 공유한다 — Opus가 메모리에 뭔가 계속 써놓긴 하는데, 정작 같은 실수를 반복하기 전에 그 메모리를 확인하는 걸 까먹는다. 그래서 "메모리 확인하라고 기억해!"가 또 메모리로 저장된다. 잘 작동하는 시스템이 아니라는 거다.

대응 전략 1 — written artifact로 정보를 세션 밖에 빼라

가장 실용적인 처방은 자동 요약에 기대지 말고, 사람이 직접 쓴 명세(spec)를 다음 세션에 인계하는 것이다. 직접 쓴 명세는 자동 요약보다 신호가 강하다. 무엇이 중요한지 사람이 직접 결정했기 때문이다.

예를 들어 작업 시작 전에 이런 식으로 PRD나 계획을 Markdown으로 남기고 저장소에 커밋한다:

# 작업 명세: 결제 모듈 리팩토링

## 목표
- PaymentService의 동기 호출을 비동기 큐 기반으로 전환

## 이미 시도했고 실패한 접근 (다시 하지 말 것)
- Redis Pub/Sub 직접 사용 → at-least-once 보장 안 됨
- 트랜잭션 내부에서 큐 발행 → 롤백 시 유령 메시지 발생

## 확정된 방향
- Transactional Outbox 패턴 사용
- 편집 대상: src/payment/outbox.ts, src/payment/PaymentService.ts

## 체크리스트
- [ ] Outbox 테이블 마이그레이션
- [ ] Outbox poller 구현
- [ ] 기존 동기 호출 제거

핵심은 "실패한 접근" 섹션이다. 이걸 명시해두면 새 세션에서 모델이 같은 실패를 반복하는 걸 막는다. 이게 원문에서 말하는 breadcrumb(빵부스러기) 접근이다 — 다음 세션이나 다음 사람이 깔끔하게 이어받을 산출물을 남기는 것.

대응 전략 2 — 메인 스레드는 조율만, 무거운 작업은 하위 에이전트로

원문 댓글 중 가장 인사이트 있던 기법이다. 사용자와의 최상위 대화 스레드에서는 도구 호출을 막고, 토큰을 잡아먹는 작업(파일 읽기, 데이터 수집, 테스트 실행)은 하위 에이전트의 재귀 호출 안에서만 돌린 뒤 요약된 결과만 메인 스레드로 반환하는 방식이다.

이렇게 하면 하위 에이전트가 5천만 토큰을 태워도 루트 대화 스레드는 10만 토큰도 안 건드릴 수 있다. 100만 LOC 코드베이스에서도 메인 대화는 스마트 구간에 계속 머무른다. 개념을 의사코드로 표현하면:

# 안티패턴: 모든 걸 평면 컨텍스트에 다 들고 다님
main_context = []
main_context += read_file("a.ts")      # +20k 토큰
main_context += read_file("b.ts")      # +30k 토큰
main_context += run_tests()            # +50k 토큰
# → 메인 스레드가 순식간에 둔한 구간으로

# 권장: 메인은 조율, 무거운 작업은 격리
def main_agent(task):
    # 도구 호출 금지. sub_agent 결과 요약만 받음
    summary = sub_agent(task="b.ts 분석 후 핵심만 보고")
    # summary는 2~3k 토큰. 메인은 스마트 구간 유지
    return decide_next_step(summary)

Claude Code에서는 이걸 부분적으로 자동으로 한다. "컨텍스트 많이 먹겠다" 싶은 휴리스틱이 작동하면 하위 에이전트(subagent)로 넘기는 식이다. 데이터 수집·집계를 하위 에이전트에 던지고 요약만 꺼내오는 패턴을 자주 볼 수 있다.

다만 트레이드오프도 있다. 원문 댓글에서 솔직하게 짚듯 재귀 깊이 1을 넘어서면 실제 성능 이득을 보기 어렵다. 외부의 기호적 재귀는 최전선 모델이 학습한 대상이 아닌 듯하다는 관찰이다. 그리고 하위 에이전트가 매번 "부트스트랩"하는 재작업 비용이 든다. 그래도 거대한 평면 컨텍스트를 들고 다니는 것보단 훨씬 효율적이라는 게 중론이다.

RAG 파이프라인이라면

RAG 운영자 입장에서 시사점은 분명하다. "컨텍스트 창 커졌으니 청킹/리랭킹 대충 해도 된다"는 유혹을 거부해야 한다. 검색해온 청크를 무작정 많이 넣을수록, 관련 없는 청크가 정작 중요한 청크의 주의력을 빼앗는다. top_k를 늘리는 게 항상 정답이 아니다. 리랭커로 정말 관련 높은 소수의 청크만 추리고, 컨텍스트를 예산처럼 다루는 게 맞다. 도움이 되는 부분은 앞쪽 일부 청크라고 가정하고 설계하는 편이 안전하다.

정리: 무엇을 봐야 하는가

한 줄 요약: 컨텍스트 창 크기는 마케팅 숫자에 가깝다. 실제로 봐야 할 건 "유효 컨텍스트를 스마트 구간 안에 유지하는 워크플로"다.

  • 코딩 에이전트를 굴리는 사람: 긴 세션을 무작정 이어가지 말고, 작업 단위마다 직접 쓴 명세(특히 "실패한 접근" 기록)를 남기고 새 세션으로 인계하라. auto-compact는 보험이지 전략이 아니다.
  • RAG 파이프라인 운영자: 컨텍스트 창이 커졌다고 청킹/리랭킹을 느슨하게 하지 마라. top_k는 신중하게. 관련 없는 청크는 노이즈다.
  • 에이전트 설계자: 메인 대화는 조율자로, 토큰 먹는 작업은 하위 에이전트로 격리하라. 단 재귀 깊이는 1로 제한하는 게 현실적이다.

마지막으로 균형을 위해 덧붙이면 — 원문 댓글에는 "최신 Opus(예: 4.8)에서는 50만 토큰을 편하게 넘긴 세션도 있었다"는 반론도 있다. 모델 버전, 하네스, 작업 종류에 따라 체감 임계점은 크게 다르다. 그러니 "100k"를 신앙처럼 외우지 말고, 본인 환경에서 어느 지점부터 회상 실수가 시작되는지 직접 로깅해서 감을 잡는 것이 가장 확실하다. 결국 컨텍스트 엔지니어링은 아직 다들 숙련도만 다른 땜장이 단계라는 자조 섞인 댓글이, 어쩌면 가장 정직한 현실 인식일지도 모른다.

참고 자료

728x90

며칠 전 사내 슬랙에 "내 노트북 메모리가 켜기만 해도 60% 넘게 먹는데 뭐가 문제냐"는 질문이 올라왔다. 범인은 Claude Desktop이었다. 채팅 한 줄 안 쳤는데도 Task Manager에 Vmmem 프로세스가 1.8GB를 잡아먹고 있었다. 이게 단순 버그가 아니라 데스크톱 AI 툴의 샌드박스 아키텍처가 어떻게 굴러가는지 보여주는 좋은 케이스라서, 실무 관점에서 한번 정리해본다.

1. 왜 지금 화제인가 — 채팅만 쓰는데 VM이 뜬다

이슈의 핵심은 단순하다. Claude Desktop Windows 앱이 Cowork(agent mode)를 한 번이라도 쓴 뒤로는, 그냥 채팅만 하려고 앱을 열어도 매번 Hyper-V VM을 띄운다는 거다. 재현 환경은 다음과 같이 보고됐다.

  • Windows 11 Pro 25H2 (Build 26200.7840)
  • Hyper-V, WSL, Docker, Windows Sandbox 전부 비활성화 상태
  • 단, VirtualMachinePlatform 기능은 켜져 있음
  • Core Isolation / Memory Integrity도 꺼져 있는 상태

여기서 재밌는 포인트는, 보고자가 wsl --shutdown을 쳐도 "not installed"가 나오고 Get-VM은 실패하는데도 VM이 뜬다는 점이다. 즉 사용자가 흔히 아는 Hyper-V 관리 인터페이스로는 잡히지 않는 경로로 VM을 생성한다는 뜻이다.

왜 VM을 띄우느냐 자체는 명확하다. Cowork/agent mode는 Claude가 사용자 머신에서 실제로 명령을 실행하고 파일을 건드리는 기능인데, 호스트를 직접 건드리게 하면 위험하니까 격리된 샌드박스 안에서 돌린다. MCP(Model Context Protocol) 기반 툴이 코드를 실행할 때 호스트 오염을 막으려는 설계다. 이 방향성 자체는 맞다. 문제는 채팅 전용으로 쓸 때도 VM을 미리 띄우고, 끌 방법을 안 준다는 것이다.

2. 동작 원리 — VirtualMachinePlatform과 Vmmem의 정체

먼저 용어 정리부터 하자. 현장에서 Hyper-V랑 VirtualMachinePlatform을 같은 거라고 착각하는 경우가 많은데 다르다.

  • Hyper-V Platform: 전통적인 Type-1 하이퍼바이저. Get-VM, Hyper-V Manager로 관리하는 그 풀스택 가상화 기능이다.
  • VirtualMachinePlatform (VMP): WSL2, Windows Sandbox, Docker Desktop 등이 쓰는 경량 유틸리티 VM 기반 기능. Hyper-V Manager에는 안 잡히지만 내부적으로는 같은 가상화 스택(vmcompute)을 쓴다.

이게 핵심이다. 보고자가 Hyper-V를 껐는데도 VM이 뜬 이유는, Claude가 Hyper-V가 아니라 VMP의 vmcompute(Host Compute Service)를 직접 트리거하기 때문이다. 보고 내용을 보면 프로세스 트리가 이렇게 나온다.

Claude Desktop
  └─ RPC interface event → vmcompute (Host Compute Service)
       └─ vmwp.exe (VM Worker Process, 부모: services.exe)
            └─ Vmmem (게스트 메모리 영역, ~1,796~1,846MB)

Vmmem은 별도 프로그램이 아니라 VM 게스트에 할당된 메모리를 호스트 Task Manager에서 보여주기 위한 가상 프로세스다. WSL2를 써본 사람이면 익숙할 거다. WSL2 게스트가 메모리를 먹으면 그게 Vmmem으로 잡힌다. 즉 Claude가 띄운 유틸리티 VM의 메모리 풋프린트가 Vmmem 1.8GB로 나타나는 것이다.

비유하자면, 컨테이너 하나만 돌리려고 Docker Desktop을 켰는데 컨테이너를 다 지워도 백그라운드 LinuxKit VM은 계속 메모리를 잡고 있는 상황과 똑같다. 다른 점은 Docker는 "내가 VM 띄웠다"고 명시적으로 알려주는데, Claude는 채팅만 쓰는 사용자한테 아무 안내 없이 조용히 띄운다는 점이다.

3. 실무 관점 — 측정, 흔한 함정, 대응 전략

실제로 뭐가 도는지 확인하기

먼저 본인 머신에서 진짜 VMP VM이 떠 있는지 확인하는 명령어다. 관리자 PowerShell에서 실행한다.

PS C:\> Get-Process vmwp, vmcompute -ErrorAction SilentlyContinue |
>>   Select-Object Name, Id, @{N='RAM(MB)';E={[math]::Round($_.WorkingSet64/1MB,0)}}

Name        Id  RAM(MB)
----        --  -------
vmcompute  4820       18
vmwp       9132     1812

vmwp가 1800MB 근처를 잡고 있으면 Claude가 띄운 유틸리티 VM이 살아있다는 신호다. Get-VM이 실패하는 환경이라도 이 프로세스는 잡힌다. Hyper-V 관리 cmdlet과는 다른 레이어라는 점을 다시 강조해둔다.

VMP 기능 자체가 켜져 있는지는 이렇게 본다.

PS C:\> Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform |
>>   Select-Object FeatureName, State

FeatureName             State
-----------             -----
VirtualMachinePlatform Enabled

흔한 함정 — 세션 파일 누적과 JSON 에러

보고에서 주목할 부분이 두 가지 있다. 첫째, %APPDATA%\Claude\local-agent-mode-sessions\ 안에 이전 Cowork 세션 파일이 2,689개 쌓여 있었다는 점이다. 세션 종료 후 정리 로직이 없다는 뜻이다. 더 황당한 건 이 파일들을 다 지우고 VM 프로세스를 죽여도, 앱을 다시 켜면 VM과 1.8GB Vmmem이 즉시 다시 생긴다는 점이다. 세션 파일이 원인이 아니라 앱 자체가 시작 시 무조건 VM을 띄우는 구조라는 거다.

둘째, Hyper-V Compute Admin 로그(이벤트 뷰어 → Applications and Services Logs → Microsoft → Windows → Hyper-V-Compute)에 부팅·앱 실행 때마다 이런 에러가 반복된다.

The specified property query is invalid:
The virtual machine or container JSON document is invalid.
(0xC037010D, 'Invalid JSON document '$'')

0xC037010D 에러는 vmcompute에 넘긴 VM 정의 JSON이 깨졌을 때 나는 거다. 빈 $ 문자열을 JSON으로 던진 흔적인데, 이게 매 실행마다 찍히는 걸 보면 VM 초기화 로직이 정상 경로를 안 타고 있을 가능성이 높다. 이벤트 로그가 이런 에러로 도배되면 모니터링 알림 노이즈가 늘어나니, 사내 SIEM에 Hyper-V-Compute 로그를 물려둔 곳이라면 미리 필터링 룰을 잡아두는 게 좋다.

리소스 영향

보고 기준으로 16GB 시스템에서 유휴 메모리 사용량이 약 50%에서 62%로 올라갔고, 일반 앱 부하가 겹치면 70~75%까지 치솟았다. 16GB 노트북에서 1.8GB를 상시 떼이는 건 무시 못 할 수준이다. 특히 VDI나 회의실 공용 PC처럼 메모리가 빠듯한 환경에서는 체감이 크다. (디스크의 경우 macOS에서는 약 10GB VM 번들을 만든다는 별도 보고가 있는데, Windows 쪽 디스크 사용량 수치는 본 보고에 명시돼 있지 않다. 환경별 확인이 필요하다.)

대응 전략

(1) Cowork를 안 쓴다면 — VMP 자체를 끈다

PS C:\> Disable-WindowsOptionalFeature -Online `
>>   -FeatureName VirtualMachinePlatform -NoRestart

Path          :
Online        : True
RestartNeeded : Possible

가장 확실한 방법이지만 부작용이 있다. WSL2, Docker Desktop, Windows Sandbox도 같이 못 쓰게 된다. 개발 머신에서 WSL2를 쓰고 있다면 이 옵션은 못 쓴다. 그리고 당연히 Cowork 기능도 비활성화된다.

(2) VMP는 살리되 VM만 매번 죽이기

PS C:\> Stop-Process -Name vmwp -Force
PS C:\> Stop-Process -Name vmcompute -Force

이렇게 죽여도 채팅 기능은 정상 동작한다. 다만 앱 재실행 때마다 다시 떠서 반복 작업이 된다. 매번 손으로 치기 귀찮으면 작업 스케줄러에 등록하거나, Claude 실행을 감싸는 래퍼 스크립트로 후처리하는 식으로 자동화할 수 있다. 단 vmcompute는 다른 가상화 기능도 공유하는 서비스라, WSL2 등을 같이 쓰는 머신에서는 vmcompute까지 죽이면 다른 VM도 영향을 받을 수 있으니 주의해야 한다.

(3) 격리 VM 안에서 Claude를 돌린다

HN 댓글에서 가장 깔끔한 해법으로 언급된 방식이다. Claude Desktop을 Windows Sandbox나 별도 Hyper-V VM 안에서 돌리고, 그 게스트 VM 안에는 VirtualMachinePlatform을 설치하지 않는 것이다. 그러면 Claude가 VMP가 없는 걸 감지하고 Cowork 탭을 그냥 비활성화한다. 실제로 "VMP가 전혀 설치 안 된 VM에서 돌리니 앱이 이를 받아들이고 Cowork를 비활성화하더라"는 보고가 있다.

다만 이건 트레이드오프가 있다. 기업 환경에서 VM 안에서 도구를 돌리면 관측 가능성(observability)이 떨어진다. 플랫폼 담당자나 보안팀 입장에선 사용자 수준 텔레메트리와 샌드박스 내부 로그가 분리되는 게 골치 아픈 지점이다. EDR(Defender, CrowdStrike 등)이 게스트 내부를 어떻게 볼지도 따로 정책을 잡아야 한다.

(4) 그냥 웹/PWA를 쓴다

냉정하게 말하면, 컴퓨터에 아무것도 접근시키지 않고 채팅만 할 거라면 데스크톱 앱을 쓸 이유가 별로 없다. HN에서도 "빠른 질문은 Claude 웹 앱을 PWA로 고정해서 쓰고, 프로젝트 작업은 CLI를 쓴다"는 운영 패턴이 여러 번 언급됐다. 채팅 전용 사용자라면 PWA가 메모리 풋프린트 측면에서 압도적으로 가볍다.

4. 정리 — 누가 언제 신경 써야 하나

한 줄 요약: Claude Desktop은 Cowork를 한 번 쓰면 그 뒤로는 채팅만 해도 시작 시 VMP 기반 유틸리티 VM을 띄워 Vmmem으로 ~1.8GB를 상시 점유하며, 현재 공식적으로 이걸 끌 토글은 없다.

  • WSL2/Docker를 안 쓰고 채팅 위주라면: VMP를 끄거나 그냥 PWA로 갈아타라. 제일 깔끔하다.
  • WSL2/Docker를 같이 쓰는 개발 머신이라면: VMP를 못 끄니, vmwp 종료 자동화나 격리 VM 방식을 고려하라.
  • VDI·공용 PC·메모리 빠듯한 환경을 운영한다면: 배포 정책에서 Claude Desktop을 통제하거나, 표준 이미지에서 Cowork 사용을 막는 가이드를 미리 만들어둬라. 1.8GB 상시 점유는 동시 세션 밀도에 직접 영향을 준다.

방향성 자체는 데스크톱 AI 툴의 자연스러운 흐름이다. 에이전트가 호스트에서 명령을 실행하려면 샌드박스가 필수고, 앞으로 나올 도구들도 대부분 비슷한 구조를 갖게 될 거다. 다만 이번 케이스의 진짜 교훈은 "기능을 안 쓰는 사용자에게는 비용을 물리지 말라"는 기본 원칙이다. 요청된 동작도 결국 "Cowork가 실제로 요청될 때만 VM을 초기화하고, 세션 종료 후 정리하고, 필요 없으면 채팅 전용 모드로 가라"는 지극히 상식적인 내용이다. 향후 버전에서 lazy initialization이 들어가는지 릴리스 노트를 지켜볼 필요가 있다.

참고 자료

728x90

+ Recent posts