
53만 행의 Zig를 11일 만에 Rust로 이식한 방법론: Anthropic이 공개한 대규모 코드 마이그레이션 6단계
요약
Anthropic이 Claude Code를 활용하여 53만 행의 Zig 코드를 Rust로 이식한 6단계 방법론을 공개했습니다. Bun 프로젝트의 사례를 통해 대규모 코드 마이그레이션의 효율성과 토큰 캐싱 전략, 그리고 검증 프로세스의 중요성을 다룹니다.
핵심 포인트
- Claude Code를 활용한 대규모 코드 마이그레이션 6단계 절차 공개
- Zig에서 Rust로의 이식을 통해 메모리 안정성 확보 및 바이너리 크기 20% 축소
- 캐시 읽기 토큰을 활용한 비용 최적화 및 반복적 루프 설계의 중요성
- 100만 행 이상의 코드 변경에도 불구하고 19건의 리그레션으로 방어 성공
레거시 코드의 전면 재작성은 지금까지 "하면 죽는" 프로젝트의 대표 격이었다. 공수를 예측할 수 없고, 도중에 중단할 수 없으며, 완료되어도 품질을 보장할 수 없다. 그래서 많은 팀은 부패한 코드베이스를 안은 채 그대로 방치한다.
2026년 7월 16일, Anthropic이 엔지니어링 블로그에 「How Anthropic runs large-scale code migrations with Claude Code」를 공개했다. 이는 추상적인 제안이 아니라, 실제로 완료된 2건의 대규모 마이그레이션에서 추출된 6단계 절차서이다. 게다가 그중 하나는 JavaScript 런타임인 Bun이 53만 행의 Zig 코드를 Rust로 이식했다는, 외부에서 검증 가능한 공개 사례다.
동시에, 이 사례는 Zig 제작자로부터 "리뷰되지 않은 slop(조잡한 결과물)"이라고 공개적으로 비판받기도 하고 있다. 본 기사에서는 먼저 무엇이 어떻게 이루어졌는지 1차 정보의 숫자로 파악하고, 그 위에 비판이 지적하는 급소를 검토한다.
Bun의 공동 창립자이자 Anthropic의 Member of Technical Staff인 Jarred Sumner는 2026년 7월 8일에 공개한 기사 「Rewriting Bun in Rust」에서 이식의 상세 내용을 숫자와 함께 공개했다.
| 항목 | 실적치 |
|---|---|
| 이식 원본 | Zig 535,496행(주석 제외), 1,448개 파일 |
| ... |
토큰 소비 내역도 나와 있다. 비캐시(non-cache) 입력이 59억 토큰, 출력이 6억 9,000만 토큰, 캐시 읽기가 720억 토큰이다. 캐시 읽기가 입력 토큰의 12배를 넘는다는 점은 이러한 종류의 루프를 설계할 때 중요한 시사점이 된다. 이식 작업의 실체는 동일한 규칙서(rulebook)와 동일한 주변 코드를 몇 번이고 다시 읽는 것이며, 그 부분이 캐시에 올라가느냐에 따라 비용이 한 자릿수 차이 난다.
동기는 유행이 아니라 구체적인 버그의 클래스였다. Sumner는 Zig의 수동 메모리 관리와 JavaScript의 가비지 컬렉터(Garbage Collector)가 혼재하는 것이 "안정성 문제의 주요 발생 원인"이었다고 말한다. 실제로 버그 목록의 큰 비중을 use-after-free, double-free, 그리고 에러 경계에서의 메모리 해제 누락이 차지하고 있었다. 이것들은 안전한 Rust에서는 애초에 컴파일 에러로서 걸러지는 종류의 문제들이다.
이식 후의 실측치도 공개되어 있다. 바이너리 크기는 Linux와 Windows에서 약 20% 축소되었고, Bun.build()를 2,000회 반복했을 때의 메모리 사용량은 v1.3.14의 6,745MB에서 609MB로 안정화되었다. 처리량(Throughput)은 25% 개선되었고, HTTP 요청 처리는 2.84.8% 증가했다. v1.3.14에서 v1.4.0 사이에 128건의 버그가 수정되었다.
이 정도 규모의 생성 코드에서 가장 중요한 것은 무엇을 근거로 "동작한다"라고 판정했는가이다. Bun의 테스트 스위트(Test Suite)는 어설션(Assertion) 수 기준으로 Debian 환경이 1,386,826건, macOS arm64가 1,259,953건, Windows가 1,007,544건이며, 테스트 파일은 모든 플랫폼에서 약 6만 개가 있다. Sumner에 따르면, 머지(Merge) 전에 6개 플랫폼 전부에서 이를 100% 통과시켰으며, 스킵(Skip)이나 삭제도 하지 않았다.
그럼에도 머지 후에 19건의 리그레션(Regression)이 발생했다(모두 수정 완료). 100만 행 이상의 차이(diff)에 대해 19건이라는 숫자를 어떻게 해석하느냐가 후술할 비판의 논점 그 자체이다.
또 다른 사례는 Anthropic Labs의 공동 리드인 Mike Krieger에 의한, Python에서 TypeScript로의 16만 5,000행 이식이다. 소요 시간은 주말 한 번 분량이었다. 투입된 것은 수백 개의 에이전트(Agent), 8개의 페이즈 게이트(Phase Gate), 3라운드의 적대적 리뷰(Adversarial Review), 그리고 최종적인 출력 패리티 체크(Output Parity Check)이다.
Bun과 달리, 이쪽에는 이식할 수 있는 기존 테스트 스위트가 없었다. 그래서 팀은 실제 세계의 시나리오 7개로 구성된 패리티 하네스(Parity Harness)를 만들었고, 원래의 Python 버전과 TypeScript 버전의 커맨드 출력을 대조하여, 동작의 차이는 모두 버그로 취급하는 방침을 채택했다. 테스트가 없다면 테스트를 대신할 판정기를 먼저 만든다는 판단이다.
블로그가 제시하는 절차는 다음 6단계이다. 순서에 의미가 있다.
- 규칙서, 의존성 맵, 갭 목록(Gap Inventory)을 만든다
- 미니 이식으로 규칙을 스트레스 테스트(Stress Test)한다
- 병렬로 전부 번역한다 (구현 에이전트와 리뷰 에이전트의 루프)
- 컴파일하고 에러를 해결한다
- 스모크 테스트(Smoke Test)를 실행한다
- 원래의 코드베이스와 동작을 대조한다
절차에 들어가기 전의 전제 조건으로서, 블로그는 강력한 판정기(judge)를 먼저 준비하라고 명시하고 있다. 이유는 명확하다. 판정기가 없다면 "종료 조건도 성공의 척도도 가질 수 없기" 때문이다.
판정기를 구축하는 데에는 구체적인 작업이 따른다. 기존 테스트를 외부에서 호출할 수 있는 것과 내부 구현에 의존하는 것으로 분류하고, 언어를 넘나들며 이식 가능한 형태로 다시 작성한다. 그리고 판정기 자체를 검증한다. 원래의 코드에 대해서는 통과하고, 의도적으로 망가뜨린 코드에 대해서는 실패하는 것 모두를 확인한다. 이 두 번째 과정을 건너뛰면, 아무것도 탐지하지 못하는 판정기를 쥐고 안심하게 되는 최악의 상황에 빠지게 된다.
룰북(Rulebook)의 내용은 이식의 성격에 따라 달라진다. Zig→Rust와 같이 구조를 유지하는 이식이라면 타입과 이디엄(Idiom)의 대응표가 주체가 된다. Python→TypeScript와 같이 설계를 다시 하는 이식이라면, 그것은 설계 문서가 된다.
흥미로운 점은 단계 2인 미니 이식(mini-migration)의 방식이다. 3체의 에이전트를 사용한다. 첫 번째 에이전트는 룰북에 따라 3개의 파일을 번역한다. 두 번째 에이전트는 동일한 3개의 파일을 "시니어 Rust 엔지니어처럼" 번역한다. 세 번째 에이전트는 그 차이점(diff)을 읽고 새로운 번역 규칙을 만든다. 그리고 번역된 파일은 모두 버린다. 블로그는 "목적은 규칙의 세련화이지, 작업을 조금 앞으로 진행하는 것이 아니다"라고 못을 박고 있다.
이 방법론의 핵심에 있는 한 문장은 이것이다. 당신이 고쳐야 할 것은 코드가 아니라, 그 코드를 만들어낸 프로세스(루프)이다.
구체적인 운용으로서, Jarred는 흔히 발생하는 8가지 실패 모드 각각에 대응하는 8체의 리뷰용 서브 에이전트를 사용하고 있다. 그리고 리뷰어가 동일한 실수를 여러 파일에서 반복해서 지적하기 시작할 때, 대처 방법은 파일 단위의 수정이 아니다. 블로그의 표현을 빌리자면, 수정은 상류(upstream)로 이동한다. 버그를 발생시킨 규칙을 한 문장 수정하고, 그 규칙이 영향을 미치는 파일만 재생성한다.
100만 행의 생성물을 파일 하나씩 수작업으로 고치는 것은 불가능하지만, 규칙을 한 줄 고쳐서 재생성하는 것은 가능하다. 이 비대칭성이 규모를 성립시킨다.
블로그가 언급하는 실무적인 주의점 중 특히 유효한 것은 다음 세 가지다.
- 인간의 시간을 앞당겨라. 룰북과 스트레스 테스트가 가장 많은 시간을 잡아먹는다. 그 이후에는 기본적으로 큐(Queue)를 태우기만 하면 된다.
- 모든 곳에 최대 모델을 사용하지 마라. 토큰 소비는 루프에 집중되므로, 루프는 의도를 가지고 설계해야 한다.
- 작업 큐는 기계적이고 재개 가능하게 만들어라. 완료의 정의는 "출력 파일이 디스크 상에 존재하는 것"으로 한다.
세 번째는 사소해 보이지만 중요하다. 수천 단위의 병렬 작업에서는 도중에 중단되는 것 자체가 전제이며, 재개 가능하지 않다면 규모를 유지할 수 없다.
이식이 에이전트에게 적합한 이유는 작업이 수천 개의 독립 단위(파일이나 크레이트(Crate))로 분할될 수 있고, 기존 코드가 그대로 사양서로 쓰일 수 있으며, 테스트라는 객관적인 검증 수단이 있고, 나아가 컴파일 에러나 테스트 실패가 다음 태스크를 자동으로 생성해주기 때문이다. 이 4가지 조건이 갖춰지지 않은 업무에 같은 방식을 그대로 가져오면 파탄 난다.
여기까지는 성공담이지만, 이 사례에는 정면적인 비판이 존재한다. 2026년 7월 14일의 The Register 기사에서, Zig의 제작자 Andrew Kelley가 이 재작성을 통렬하게 비판했다.
Kelley의 논점은 크게 세 가지다. 첫째, 품질 문제는 AI 이전부터 존재했다는 지적으로, Sumner는 "LLM을 손에 넣기 훨씬 전부터 slop(엉망인 코드)을 써왔다"고 언급했다. 둘째, 리뷰되지 않은 AI 생성 코드는 "앞으로 수많은 문제를 일으킬 것"이라는 우려로, Zig 프로젝트 자체가 AI에 의한 기여를 받아들이지 않는 방침을 취하는 이유도 거기에 있다고 보았다.
그리고 세 번째가 기술적으로 가장 날카로운 질문이다. Kelley는 이렇게 묻고 있다. 테스트 스위트가 "Zig 코드의 버그를 잡기에는 불충분한데, 리뷰되지 않은 100만 행의 slop의 버그를 잡기에는 충분하다는 말인가". 그는 최종적으로 이것은 언어의 우열 문제가 아니라 "두 프로젝트의 가치관 차이" 문제라고 총괄했다.
이 비판은 무시할 수 없다. 왜냐하면 Anthropic의 방법론 자체가 판정기의 질에 모든 것을 거는 구조이기 때문이다. 룰북도 적대적 리뷰(Adversarial Review)도 패리티 하네스(Parity Harness)도, 최종적으로는 "테스트를 통과했는가"로 귀결된다. 테스트가 포착할 수 없는 종류의 결함은 이 체제에서 원리적으로 탐지되지 않는다. 머지(Merge) 후 발생한 19건의 리그레션(Regression)은 바로 그 틈새에서 나온 것이다.
공정하게 보자면, 양측의 주장은 모두 성립한다. 130만 건 이상의 어설션 (Assertion)을 6개 플랫폼에서 100% 통과시켰다는 것은, 임의의 AI 생성 코드와는 비교할 수 없는 검증 수준이다. 동시에 테스트 커버리지 (Test Coverage)가 포착하지 못하는 영역이 존재한다는 것 또한 사실이며, 100만 행의 차이(diff)를 인간이 읽지 않은 이상, 그 영역은 완전히 미검사 상태로 남는다. 이식 전의 Zig 코드 역시 동일한 테스트로 보호받고 있었으므로, 판정기의 그물망이 성긴 것은 이식 전후로 변하지 않았다는 관점도 가능하다.
요컨대, 이 방법론이 보장하는 것은 "이식 전과 동일한 품질 기준을 충족하는 것"이지, "좋은 코드라는 것"이 아니다. 무엇을 보장하지 않는지를 이해한 상태에서 채택한다면, 이는 유용한 도구이다.
자신의 프로젝트에서 시도하기 전에, 판정기의 실력을 측정해 두어야 한다. 기존 테스트를 의도적으로 망가뜨린 코드에 실행해 보고, 얼마나 잡아내는지 확인하라. 여기서 많은 오류가 그냥 통과된다면, 이식을 시작하기 전에 해야 할 일은 테스트를 작성하는 것이지, 에이전트 (Agent)를 배치하는 것이 아니다.
이 정도 규모의 워크로드 (Workload)는 툴 측면에도 흔적을 남겼다. Claude Code의 체인지로그 (Changelog)를 보면, v2.1.20에서 동적 워크플로우의 규모를 제어하는 "Dynamic workflow size" 설정이 추가되었고, v2.1.212에서는 /fork가 대화를 새로운 백그라운드 세션 (Background Session)으로 복제하는 동작으로 변경되었으며, 세션 내에서 서브 에이전트 (Sub-agent)를 실행하던 기존 기능은 /subtask로 분리되었다.
같은 v2.1.212 버전에는 폭주 방지를 위한 상한선도 포함되어 있다. 서브 에이전트 생성은 세션당 기본 200개(CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION로 변경 가능)이며, 웹 검색 (Web Search) 호출 역시 기본 200개(CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION)에서 제한된다. 수십 명의 에이전트를 며칠 동안 돌리는 운용이 현실화됨에 따라, 위임 루프 (Delegation Loop)나 서치 루프 (Search Loop)가 폭주했을 때의 피해를 무시할 수 없게 되었다는 의미일 것이다.
이 사례에서 얻어야 할 점을 정리한다.
먼저 비용의 규모다. 53만 행의 이식에 16만 5,000달러, 1달러를 150엔으로 환산하면 약 2,475만 엔이다. 소스 코드 1행당 약 0.31달러이다. 인간 팀이 동일한 규모의 재작성을 견적 냈을 때의 숫자와 비교하면, 판단 근거로서 충분히 구체적일 것이다. 다만 이는 모델 비용일 뿐이며, Sumner는 11일 동안 거의 내내 출력을 읽으며 문제를 발견하고 루프를 수정하는 프롬프트 (Prompt)를 계속 작성했다. 인간의 개입이 제로가 된 것이 아니라, 개입하는 위치가 코드에서 루프로 이동했을 뿐이다.
다음은 적용 범위다. 앞서 언급한 4가지 조건(병렬 분할 가능, 구 코드가 사양(Specification)임, 객관적 검증 가능, 큐(Queue)의 자기 생성)이 갖춰진 작업은 이식 외에도 있다. 대규모 리팩터링 (Refactoring), 타입 (Type) 도입, API 버전의 일제 추종 등은 동일한 구조를 가진다. 반대로, 사양이 사람의 머릿속에만 있는 신규 기능 개발은 이 유형에 해당하지 않는다.
마지막으로 태도에 관한 이야기다. 블로그에 있는 또 다른 한 문장이 이러한 종류의 작업에 임하는 마음가짐을 잘 나타낸다. 최악의 경우라도, 브랜치 (Branch)를 삭제하고 다시 시작하면 그만이다. 이식 작업이 기존 코드에 손을 대지 않고 독립된 브랜치에서 완결되는 한, 실패 비용은 투입한 토큰 (Token) 비용으로 한정된다. 이 가역성 (Reversibility)이야말로, 지금까지 착수할 수 없었던 규모의 작업에 도전할 수 있게 된 진짜 이유일지도 모른다.
How Anthropic runs large-scale code migrations with Claude Code (2026년 7월 16일)
Rewriting Bun in Rust (2026년 7월 8일)
Zig creator calls Bun's Claude Rust rewrite 'unreviewed slop' (2026년 7월 14일, The Register)
Claude Code CHANGELOG
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기