원문: https://newsletter.pragmaticengineer.com/p/the-pulse-what-can-we-learn-from-07f 저자: Gergely Orosz (Pragmatic Engineer)
지난주 샌프란시스코에서 나는 JavaScript 런타임 Bun의 제작자 Jarred Sumner를 만났고, Bun의 Zig에서 Rust로의 재작성에 대해 더 많이 알고 싶었다. 당시 Jarred는 마이그레이션에 사용된 도구인 Fable이 미국 정부의 수출 규제로 인해 작동 불능 상태였기 때문에 많은 말을 하기를 원하지 않았다.
다행히 상황이 해결되어 Fable이 전 세계적으로 이용 가능해졌고, Jarred는 이 프로젝트에 대한 상세한 포스트를 공개했다. 마이그레이션 내용에 들어가기 전에 약간의 배경 설명이 필요하다.
Bun이란?
Bun은 복잡한 프로젝트다. 수많은 프로덕션 소프트웨어가 Bun에 의존하고 있다. Bun은 다음과 같은 다양한 기능을 수행한다:
- JavaScript, TypeScript, CSS 트랜스파일링, 미니파이, 번들링
- 테스트 러너
- 패키지 매니저 (npm 호환)
- 기타: 모듈 해석, WebSocket 클라이언트, Node.js 구현체 및 여러 모듈
현재 Bun은 월간 2,200만 다운로드를 기록하며, Claude Code, OpenCode 같은 소프트웨어가 Bun에 의존하고, Vercel, Railway, DigitalOcean 같은 호스팅 업체들도 Bun을 자사 지원한다.
왜 재작성했나?
Zig는 메모리 안전 언어가 아니며, 메모리 관련 버그가 지속적으로 발생했다. Jarred는 최신 버전의 Bun에서 발생하는 메모리 관련 버그 목록을 공개했다: 메모리 누수, 메모리 문제로 인한 크래시, 힙 범위 초과 쓰기 등. 이는 Bun 팀이 메모리 관련 문제를 줄이기 위해 Zig 컴파일러에 패치를 적용하고 엔드투엔드 메모리 누수 테스트를 도입한 이후에도 발생한 것들이다. Jarred의 말을 빌리자면:
"우리 버그픽스 목록이 너무 나빠 보였고, 나는 Bun의 크래시 걱정으로 잠자리에 들기가 지쳤다. Zig를 탓하는 게 아니다 — Zig를 쓰는 다른 사용자들은 우리가 겪는 버그가 없고, GC와 수동 메모리 관리를 혼합하는 것은 어떤 언어도 제대로 설계하지 않는 드문 케이스다. (...)
Bun에서는 가비지 컬렉션 값과 수동 관리 값의 수명을 올바르게 처리하는 것이 주요 안정성 문제의 근원이었다 — 대부분은 작은 메모리 누수이고 때로는 크래시다. 모든 메모리 할당은 꼼꼼하게 검토해야 했다. 이 바이트들은 어디서 해제되나? 한 번만 해제되도록 어떻게 보장하나? JavaScript 예외를 제대로 체크했나? 이 가비지 컬렉션 포인터가 보수적 스택 스캐너에 보이나? 이건 가비지 컬렉션 메모리인가, 수동 관리 메모리인가?"
메모리 안전하면서도 성능이 좋은 언어로 이전하면 이러한 오류를 없앨 수 있고, Rust가 그 조건에 맞는 언어였다. Jarred의 말:
"그 목록에서 발생하는 버그의 상당 부분이 use-after-free, double-free, 오류 경로에서 'free를 잊어버림'이다. 안전한 Rust에서는 이것들이 컴파일러 오류이며 Drop을 통한 RAII 방식의 자동 정리가 된다. 컴파일러 오류는 스타일 가이드보다 더 나은 피드백 루프다."
완전한 Rust 재작성의 딜레마
그러나 완전한 Rust 재작성은 항상 끔찍한 아이디어였다. 아니, 적어도 AI가 등장하기 전까지는 그랬다. 재작성에는 두 가지 문제가 있다: 너무 오래 걸린다, 그리고 훨씬 더 오래 걸린다. 재작성을 경험해본 개발자라면 이런 흐름을 알 것이다:
- 얼마나 걸릴지 추정한다. 예를 들어 9개월.
- 9개월 후, 원본 코드베이스에 새 기능이 추가되어 그것도 추가해야 해서 ~6개월이 더 남는다.
- 15개월 후에도 같은 이유로 몇 달이 더 남아있다.
- 결국 2개월 "기능 동결"을 강제하고 ~18개월에 재작성을 완료한다. 9개월 추정이 2년 이상이 된 것이다.
Bun의 Zig 재작성에 대해 Jarred는 이렇게 말했다:
"역사적으로 재작성은 끔찍한 아이디어다. 주석을 제외하면, Bun은 535,496줄의 Zig 코드다.
다른 언어로의 재작성은 작은 팀의 엔지니어들이 1년을 꽉 채워야 할 작업이다.
사용자에게 아무런 영향을 미치지 않는 1년은 우리가 고려할 수 있는 현실적인 선택지가 아니었다. 그래서 코드 스타일 강제를 통한 안정성 수정이 최선의 방법이었고, 우리가 Bun 코드베이스에 Rust에서 영감을 얻은 스마트 포인터를 추가했을 때의 계획이었다.
하지만 솔직히 나는 그렇게 하기 싫었다. 자체 제작 스마트 포인터는 Rust보다 나쁜 인체공학에 Rust의 보장 중 어느 것도 제공하지 않는다.
만약 내가 Anthropic의 새 모델 [Fable]이 Bun을 Rust로 재작성할 수 있는지 테스트하는 데 1주일을 쓴다면 어떨까?"
Fable을 이용한 Bun 재작성
재작성은 단순히 "Claude야, Bun을 Rust로 재작성해. 실수 없이."라는 프롬프트를 입력하는 것만큼 간단하지 않았다. Jarred가 수행한 방법은 다음과 같다:
Step 1: 준비 작업 (3시간)
코드 작성 전에 Claude와 약 3시간의 집중적인 준비 작업을 진행했다:
"코드 작성 전에, 나는 Claude와 약 3시간을 함께 우리 Zig 코드베이스의 패턴을 Rust에 가깝게 매핑하는 방법에 대해 이야기했다. Claude는 이 논의를 PORTING.md 문서로 직렬화했고, 이는 해커뉴스에 [Zig→Rust 이식 가이드]로 올라갔다."
이 가이드는 다음과 같은 지침이 담긴 600줄 파일이었다:
기본 규칙:
- tokio, rayon, hyper, async-trait, futures 금지. std::fs, std::net, std::process 금지. Bun은 자체 이벤트 루프와 시스템콜을 소유한다.
- async fn 금지. 모든 것은 콜백 + 상태 기계, Zig와 동일.
- 차용 검사기 재구성은 허용된다. Zig 흐름을 매핑할 때 &mut가 겹치면 필요한 스칼라(.len(), 인덱스)를 로컬에 캡처하고, 차용을 삭제한 다음 다시 차용하라. 차용 검사를 무력화하기 위해 raw 포인터를 사용하지 말라...
Step 2: 시험 실행 + 적대적 리뷰
1,448개 전체 파일 중 3개 파일 재작성 시험. 이후 Jarred는 Claude가 변경한 것과 다른 세션에서 두 개의 별도 적대적 리뷰를 실행해 결과를 비판적으로 검토했다.
Step 3: 64개 AI 에이전트로 작업 분산
Jarred는 에이전트들이 서로 독립적인 파일에서 병렬로 작업하도록 작업을 분리했다.
Step 4: 실행 중 문제 해결 (~1일)
모든 실행을 시도했을 때, 에이전트들이 서로 방해를 받기 시작했다:
"Claude에게 1,448개의 .zig 파일 전체에서 워크플로우를 반복하도록 요청했는데, 약 2분 후 한 Claude가 커밋 전에 git stash를 실행했다. 다른 Claude가 git stash pop을 실행했다. 그리고 git reset HEAD --hard를 실행했다. 서로 밟고 있었다! 각 Claude를 별도 워크트리에 넣으면 Bun의 git 저장소가 너무 크고 최종적으로 변경 사항을 함께 컴파일하고 확인해야 하기 때문에 디스크 공간이 부족해질 것이었다.
그래서 나는 Claude에게 워크플로우를 편집해 Claude에게 특정 파일을 한 번에 커밋하는 git 명령어 외에 git stash, git reset 또는 다른 git 명령어를 절대 실행하지 말도록 지시했다. cargo도 안 된다. 느린 명령어는 전혀 안 된다.
그런 다음 Claude가 워크플로우를 재개했다. 작동했다! 너무 느렸기 때문에, 4개의 워크플로우 샤드로 분할해 각각 자체 워크트리(총 4개)에서 16개의 Claude가 파일을 커밋하고 푸시했다."
Step 5: 실행 및 대기 (~2일)
병렬 에이전트들이 작업을 시작하여 이틀에 걸쳐 535,496줄의 Zig 코드를 재작성했다. 각 커밋은 두 개의 적대적 리뷰를 거친 후 커밋되었다.
Step 6: ~1,600개 컴파일러 오류 수정 (~12시간)
재작성이 완료되었지만 아무것도 컴파일되지 않았다. 크레이트(Rust의 최상위 컴파일 단위) 별로 Jarred는 Claude가 컴파일러 오류를 수정하게 했다:
"순환 의존성 수정으로 약 16,000개의 컴파일러 오류가 드러났다. 1명의 인간에게는 방대한 양이지만, 64개의 Claude에게 동시에는 미친 숫자가 아니다.
병렬성을 극대화하기 위해, 워크플로우가 각 크레이트를 루프로 돌았다.
각 크레이트에 대해: cargo check를 실행하고, 출력을 파일별로 그룹화하고 오류를 파일에 저장 → 해당 크레이트 내의 모든 컴파일러 오류 수정 → 2명의 적대적 리뷰어 → 1명이 수정 사항 적용."
에이전트들이 자정부터 오전 11시 30분까지 컴파일 버그를 자율적으로 수정했다 — Jarred와 팀이 잠을 자는 동안.
Step 7: 로컬 테스트 실행 (~2일)
Bun에는 대규모 테스트 스위트가 있다. 다음 단계는 컴파일 오류 없이 테스트를 실행하는 것이었다.
Step 8: CI 테스트 스위트 통과 (~3일)
테스트가 실행(그리고 실패)되기 시작하면, 다음 단계는 테스트가 통과하도록 코드를 수정하는 것이었다.
완료: 11일!
모든 테스트가 통과하고 Jarred가 모든 것이 예상대로 작동하는지 확인한 후, 변경 사항을 병합했다. 전체 과정은 계획부터 완료까지 11일이 걸렸다.
이 과정은 얼마나 반복 가능한가?
재작성 비용은 API 가격 기준으로 무려 $165,000이었다. Fable의 API 가격 기준으로 59억 개의 캐시되지 않은 입력 토큰, 6억 9천만 개의 출력 토큰, 720억 개의 캐시된 입력 토큰 읽기를 소비했다. 이는 미국 중견 기업 소프트웨어 엔지니어의 연봉 기본급에 해당하는 금액이다!
하지만 엔지니어가 1년 동안 이 모든 작업을 할 수 있었을까? 아마도 아닐 것이다. Mitchell Hashimoto도 같은 말을 한다:
"비용 측면에서, Fable(검증 안 했지만)의 API 가격으로 $165,000은 믿을 수 없을 정도로 훌륭한 거래다. 11일 동안 Claude가 달성한 마일스톤을 그 급여로 받는 엔지니어가 달성했을 가능성은 절대적으로 없다. 절대로. (비용을 N명의 엔지니어에게 $165K 총액으로 분배하더라도 11일로는 수학이 맞지 않는다)
그러나 이는 또한 내 자신의 편견을 재확인해준다 — Fable은 특히 명확한 보상 함수가 있는 어렵고 집중된 작업에 매우 탁월하다."
AI가 이전에는 고려조차 못했던 재작성과 마이그레이션을 가능하게 하면 어떨까? AI 없이 Bun을 Rust로 재작성하는 것은 비현실적이었다고 Jarred는 인정한다:
"수작업으로는, 코드베이스에 대한 완전한 컨텍스트를 가진 소규모 엔지니어 팀이 1년이 걸렸을 것이다. 그 기간 동안 우리는 Node.js 호환성을 개선하거나, 버그를 수정하거나, 보안 문제를 수정하거나, 새로운 기능을 구현할 수 없었을 것이다. 우리는 절대 그렇게 하지 않았을 것이다."
재작성이나 마이그레이션이 수개월 또는 수년이 걸리기 때문에 이러한 프로젝트들이 결코 실행되지 않는 것이다. 비용은 잠시 제쳐두고 이 질문을 고려해보자: AI가 1년짜리 재작성을 1주일로 단축할 수 있다면, 당신은 하겠는가?
만약 답이 "물론이지"라면, 이제 Bun 마이그레이션의 형태로 블루프린트가 존재한다. 물론 글에서 자세히 설명되지 않은 몇 가지 주의사항이 있다:
- 코드베이스에 매우 동기 부여된 엔지니어가 필요하다
- 테스트 스위트가 통과하면 작동한다는 것을 알 수 있도록 매우 강력한 테스트 스위트가 필요하다
- 얼마나 잘 작동할지 모르는 상태에서 많은 토큰에 투자할 의지가 필요하다
공정하게 말하면, #3은 LLM이 코드 마이그레이션 같은 "일상적인" 작업에 상당히 능숙하다는 것을 알기 때문에 가장 약한 포인트다. 좋은 테스트 스위트(#2)와 문제를 해결할 동기 부여된 엔지니어(#1)가 있다면, 성공할 가능성이 높다.
남은 질문은 얼마나 많은 비용을 사용할 수 있는가다. $165K가 되지는 않을 것이다: 더 단순한 프로젝트나 모델 사용에 신중함으로 비용을 줄일 수 있다. 예를 들어, 가장 비싼 모델로 고수준 계획을 수립하고, 코딩과 리뷰 작업에는 더 저렴한 것을 사용하라.
AI를 통한 마이그레이션은 분명히 가속화되고 있다 — 하지만 Bun처럼 잘 엔지니어링된 프로젝트에서만 가능하다.
핵심 요약 (20줄)
- Bun은 월간 2,200만 다운로드의 JavaScript 런타임으로 Claude Code, OpenCode 등이 의존한다.
- Zig로 작성된 535,496줄 코드에서 메모리 관련 버그(누수, 크래시, 힙 오버플로우)가 지속 발생했다.
- Rust의 메모리 안전 보장이 해결책으로 선택됐다 — 컴파일러 오류가 런타임 크래시보다 낫다.
- 수작업 재작성은 3명의 엔지니어가 1년을 꽉 채워야 했기 때문에 현실적으로 불가능했다.
- Jarred Sumner는 "Fable(Claude AI)로 1주일만 테스트해보면 어떨까?"라는 아이디어를 시도했다.
- 3시간의 집중 준비 작업으로 Claude와 함께 600줄짜리 PORTING.md 가이드를 작성했다.
- 3개 파일 시험 재작성 후 별도 세션에서 2개의 적대적 리뷰를 실행했다.
- 총 64개 AI 에이전트를 병렬 실행해 1,448개 파일을 분산 처리했다.
- 에이전트들이 git 명령어로 충돌하는 문제를 발견, 워크플로우를 수정해 4개 워크트리로 분리했다.
- 이틀 동안 535,496줄 Zig 코드가 Rust로 재작성됐다 — 각 커밋에 2개의 적대적 리뷰 적용.
- 재작성 완료 후 ~16,000개 컴파일러 오류가 드러났고, 64개 에이전트가 밤새 자율적으로 수정했다.
- 이후 로컬 테스트 실행 (~2일), CI 테스트 통과 (~3일) 과정을 거쳤다.
- 총 소요 기간: 계획부터 병합까지 11일.
- 총 비용: API 가격 기준 $165,000 (59억 캐시미스 입력 토큰 + 720억 캐시 토큰).
- Mitchell Hashimoto: "11일 동안 Claude가 달성한 것을 그 급여로 받는 엔지니어가 달성할 가능성은 절대적으로 없다."
- 이 접근법의 조건: ① 코드베이스에 깊은 이해를 가진 동기 부여된 엔지니어 ② 강력한 테스트 스위트 ③ 토큰 비용 감수 의지.
- AI 코딩이 가장 탁월한 영역 — 명확한 보상 함수가 있는 어렵고 집중된 작업 (예: 코드 마이그레이션).
- 고수준 계획은 비싼 모델, 코딩/리뷰는 저렴한 모델 사용으로 비용 절감 가능.
- 핵심 교훈: AI는 "이전에는 불가능했던 재작성"을 가능하게 만든다.
- AI 마이그레이션 가속화는 현실이지만, Bun처럼 잘 설계된 프로젝트(강력한 테스트 스위트)에서만 성공한다.