← 글 목록
Report · Bun v1.4 · 2026-08-20 공개

런타임을 갈아엎었는데,
사용자는 더 조용해졌다

Bun v1.4 리포트. 2026-08-20 공개분을 2026-08-21에 정리했어요. 도리, blog.justdori.com. Bun 1.4는 백만 줄짜리 코드를 Zig에서 Rust로 옮긴 릴리스예요. 그런데 진짜 성과는 "Rust로 바꿨다"가 아니라 다른 데 있어요.

Claude Code를 포함한 실제 사용자가 거의 눈치채지 못한 채 CPU와 메모리, 시작 시간, 호환성이 같이 좋아졌다는 점입니다. (v1.4 릴리스 노트, bun-in-rust)

공식 발표 수치는 이렇습니다. GitHub 이슈 2,900개 이상 수정, Node 테스트 1,517개 새로 통과, 유휴 CPU 5배 감소, 메모리 최대 35% 감소, Linux 시작 시간 최대 50% 단축. (@bunjavascript )

01.

릴리스 버튼을 누르기까지: "3 hours" 뒤에 나온 것은 기능 묶음이 아니었다

Bun 1.4.0은 2026년 8월 20일에 나왔어요.

공식 계정의 발표는 "Bun 1.4 is here."

한 줄이었습니다. 그 뒤에는 Bun 1.3.14 이후 2,036개 커밋이 있었고, 그중 1,375개가 .rs 파일을 하나 이상 건드렸어요. .zig 파일을 건드린 커밋은 21개뿐이고, 7월부터는 Zig 변경이 아예 없습니다.

양쪽을 다 건드린 커밋이 19개, 어느 쪽도 아닌 커밋이 659개예요. 이번 릴리스를 "기능이 많은 업데이트"보다 "프로젝트의 개발 기반이 교체된 업데이트"로 읽어야 하는 이유죠. (compare bun-v1.3.14...bun-v1.4.0, commit 23427db)

2026-05-12부터 08-20까지 100일을 달별로 나누면 이렇게 됩니다.

5월에는 커밋 266개(.rs 227, .zig 15). PR #30412 Rust 포트가 머지됐고, 메모리 누수 대청소(#30875)와 Clippy 규칙 고정(#31116)이 뒤따랐어요.

6월은 353개(.rs 240, .zig 6). JavaScriptCore와 메모리 할당기(allocator)를 같이 쓰게 한 #34009가 들어갔고, Claude Code 내부 롤아웃이 있었습니다.

7월은 752개(.rs 460, .zig 0)로 Node 호환성 작업이 몰렸어요. Jarred Sumner는 이때 "merging some more node.js compatibility improvements and then will release bun v1.4."

라고 썼습니다. (7월 발언)

8월은 665개(.rs 448, .zig 0). pnpm parity PR #38333, 출시 지연, 그리고 "3 hours"까지 갑니다.

릴리스 직전은 거대한 인프라 전환치고는 묘하게 평범했어요. 공식 계정이 "Bun v1.4 is compiling"이라고 올렸고(X), 이틀 뒤 "Want to merge a few more things"라며 일정을 미뤘습니다(X). 출시 당일 Jarred는 "things are uploaded"라고 쓰고(X), 마지막으로 "3 hours"만 남겼어요(X).

거대한 전환이 마지막 순간에는 영상 편집하고, 업로드하고, 몇 개 더 머지하는 흔한 출시 업무로 수렴한 셈이죠.

결과표는 흔하지 않았어요.

공식 요약에는 이슈 2,900개 이상, Node.js 테스트 1,517개, 유휴 CPU 최대 5배, 메모리 최대 35%, Linux 시작 최대 50%, 그리고 Zig에서 Rust로의 재작성이 같이 적혀 있습니다.

Rust 이식이 끝나자마자 낸 게 아니었어요. 호환성과 운영 성능까지 출시 기준으로 올려 두고 버튼을 눌렀습니다. (@bunjavascript )

그래서 시간순으로 봤어요. Rust 전환이 먼저 있고, 그 뒤에 메모리 누수 제거, 공용 allocator, Node 호환성, pnpm parity, 출시 지연이 차례로 붙는데, 이걸 보면 1.4의 본질이 보여요.

큰 PR 하나로 끝난 게 아니고, 그 PR을 실제 제품으로 바꾸는 후속 작업으로 이어졌어요. (#30412 , #30875, #34009, #38333)

02.

왜 Rust였나

Bun 안에는 JavaScriptCore의 가비지 컬렉션 세계와, 수명을 직접 관리하는 네이티브 코드가 같이 있어요.

Jarred가 공개한 과거 버그 목록이 길어요. node:zlib의 use-after-free, node:http2 내부 포인터 무효화, UDP 전송 중 ArrayBuffer detach, CSS 파서 double-free, fs.watch() GC root 누수가 있고, tlsSocket.setSession()은 호출할 때마다 SSL 세션(SSL_SESSION)을 약 6.5KB씩 흘렸습니다. Zig 자체가 나빴다기보다는 이쪽이 문제였어요.

GC 객체와 수동 메모리의 경계를 호출 지점마다 맞게 다루는 일이 이 규모에서는 계속 사람의 주의력에 걸려 있었습니다. (just be really smart, why-rust)

Jarred는 이 결정을 Zig 비판으로 쓰지 않았어요. Bun의 첫 Zig 코드는 2021년 4월 16일에 작성됐고, 초기 버전은 LLM이 없던 시절 1년쯤 걸려 만들었습니다.

지금 Bun CLI는 한 달에 2,200만 번 넘게 다운로드돼요. 같은 글에 "Zig made Bun possible"이라는 감사와 "going to sleep worrying about crashes"라는 피로가 같이 들어 있습니다.

과거의 선택이 틀려서라기보다, 프로젝트가 커지면서 실패 비용과 피드백 루프가 달라졌기 때문에 바꾼 거예요. (bun-in-rust)

"Compiler errors are a better feedback loop than a style guide." (Jarred Sumner)

Rust를 고른 이유는 화려한 언어 취향보다 오류를 언제 잡느냐였어요. use-after-free, double-free, 오류 경로의 free 누락은 safe Rust에서 컴파일 오류가 되거나 Drop으로 정리됩니다.

Zig 코드는 주석 빼고 535,496줄이었어요. 보통 하던 대로 다시 짰다면 작은 팀이 1년쯤 기능 개발과 보안 수정까지 사실상 얼려야 했을 겁니다.

이런 이유로 아키텍처를 새로 설계하지 않고, 기존 Zig를 Rust로 기계적으로 옮겨서 동작 차이를 최소화하는 쪽을 골랐어요. (claude-rewrite-bun-in-rust)

PR #30412는 +1,009,257 / −4,024줄, 파일 2,188개, 이슈 댓글 708개, 인라인 리뷰 420개였습니다(GitHub 집계).

바이너리는 3~8MB 줄었어요. PR 본문에는 이렇게 적혀 있습니다.

No async rust.

The binary size shrinks by 3 MB - 8 MB

Rust 생태계 관습을 통째로 들이지 않았어요. 기존 이벤트 루프와 구조는 그대로 두고, 소유권과 정리 규칙만 컴파일러가 볼 수 있게 옮겼습니다.

03.

백만 줄을 어떻게 리뷰했나

"Claude한테 Bun을 Rust로 바꿔 달라고 했다"로 줄이면 중요한 걸 거의 다 놓쳐요.

Jarred는 동적 워크플로 약 50개를 11일 동안 돌렸습니다. 먼저 3시간쯤 들여 Zig 패턴을 Rust 패턴으로 옮기는 규칙을 PORTING.md에 정리했고, Zig 파일 1,448개 전체에 들어가기 전에 3개 파일로 시험했어요. 본 작업에서는 구현하는 Claude 하나에 적대적으로 리뷰하는 Claude를 둘 이상 붙였고, 리뷰와 수정이 끝나야 커밋했습니다. (loops , prep-work, trial-run)

나중에는 worktree 4개에 Claude를 16개씩, 피크 약 64개를 병렬로 돌렸어요. 역할은 구현 1, 리뷰 2, 수정 1. 원문 그림은 이 과정을 리뷰 파이프라인 3단계라고 불렀어요.

컴파일 오류 약 16,000개가 작업 큐가 됐고, 포트 브랜치 커밋은 merge 제외 6,502개, 피크는 시간당 695개와 분당 58개, 코드는 분당 약 1,300줄이었습니다. CI 실패 파일은 이틀 만에 972개에서 23개로 줄었고, 6개 플랫폼이 전부 초록이 된 Buildkite build #54202 뒤에 머지됐어요. (false-starts , compiler errors, writing the code, stats, CI)

제일 중요한 건 컨텍스트를 나눈 거였어요.

구현자는 원본 Zig와 포팅 계획, 자기 추론을 봤습니다. 리뷰어는 diff만 받고 "이 코드는 틀렸다고 가정하고 이유를 찾아라"라는 역할을 맡았어요.

이 리뷰가 컴파일이 이미 되는 코드에서 버그를 찾았습니다. 비동기 uv_close 뒤의 use-after-free와 double-free, 1970년 이전 시각에서 trunc 때문에 생긴 음수 나노초, eager unwrap_or가 일으킨 panic.

모델 하나의 "자기 검토"가 아니라 이해관계가 다른 리뷰 흐름을 만든 거예요. (split-context-windows)

공개 PR에 제일 매운 질문이 달렸습니다.

make sure Claude didn't change the tests to "fix" the problem

@skyfallwastaken이 단 댓글이에요(PR 댓글). Claude가 문제를 고친 게 아니고 테스트를 고쳐서 통과시킨 건 아닌지 보라는 말이었고, 백만 줄 PR에 달릴 수 있는 가장 타당한 질문이었습니다. Jarred는 머지 전에 모든 플랫폼에서 기존 테스트가 실제로 돌았고 건너뛴 게 없는지 손으로 확인했다고 썼습니다.

지우거나 스킵한 테스트는 0개였어요. (merging, 0 tests skipped)

순탄하지도 않았어요. 처음엔 Claude들이 각자 git stash, stash pop, reset --hard를 실행하면서 서로 작업을 덮어썼습니다.

그래서 worktree 4개에 16개씩 나눠 앉혔어요. 분당 약 1,300줄이라는 속도보다는, 망가진 작업 방식을 프롬프트와 워크플로 규칙으로 고쳐서 다시 돌렸다는 게 따라 할 만한 부분입니다. (false-starts , stats)

머지 시점의 테스트 규모는 이랬어요.

Debian x64에서 expect() 1,386,826회, 테스트 60,624개, 파일 4,174개였어요.

macOS arm64는 expect() 1,259,953회, 테스트 58,850개, 파일 4,175개, Windows x64는 expect() 1,007,544회, 테스트 57,337개, 파일 4,173개입니다.

tests skipped or deleted = 0. (0 tests skipped)

백만 줄을 사람이 다 읽지는 못했어요. 그 대신 언어와 상관없는 테스트, 분리된 적대적 리뷰, 플랫폼별 CI, 손으로 하는 샘플링을 겹쳤습니다.

04.

머지는 끝이 아니었다

PR #30412가 머지된 날은 새 개발 기반의 시작에 가까웠어요.

바로 뒤에 나온 PR #30875는 제목부터 "Fix effectively every native-code memory leak in Bun"이었고, 파일 285개에 +5,004 / −1,822, 이슈 댓글 8개, 인라인 36개였습니다.

Rust로 옮겼다고 누수가 저절로 사라졌다고 하지 않았어요. 새 도구와 sanitizer가 드러낸 소유권 문제를 한꺼번에 정리했고, 반복 빌드 메모리 곡선이 평평해진 직접적인 이유가 이 PR입니다.

다음 날 나온 PR #31116은 Clippy lint 33개를 deny로 묶었어요.

위반 2,735건, 파일 1,036개, +18,626 / −23,043, 인라인 139개. 추가보다 삭제가 많았습니다.

컴파일된다에서 멈추지 않고, 앞으로 들어올 Rust 코드가 같은 빚을 다시 쌓지 못하게 빌드 규칙으로 막았습니다. 백만 줄을 넣은 바로 다음 날 팀이 한 일은 새 기능이 아니고 lint 위반 2,735건 정리였어요.

PR #34009는 파일 42개, +486 / −145, 이슈 댓글 24개, 인라인 142개입니다.

설명은 "Bun and JavaScriptCore now share the same memory allocator". 원래는 JavaScriptCore가 libpas를, Bun이 mimalloc을 따로 썼는데 1.4부터 JSC도 mimalloc을 쓰고, 쉴 때 메모리를 돌려주는 scavenger 같은 기능이 더해졌어요. HTTP 서버 메모리와 오래 도는 프로세스의 CPU가 줄어든 게 이 덕분입니다. (Jarred 설명 , v1.4 production)

PR #31491은 반대로 hot path를 C++ SIMD로 옮겼어요.

파일 17개, +1,113 / −153, 이슈 댓글 16개, 인라인 36개.

Make it faster than it was in Bun v1.3.14 (zig) and on main.

xxHash3는 C++ Highway SIMD와 런타임 디스패치로 갔고, Bun.stringWidth와 HTML escape, ANSI scan도 C++/SIMD 경로를 탔습니다.

소유권과 구조는 Rust가 맡고, CPU를 많이 먹는 경로는 C++과 SIMD 커널을 계속 써요. (#31491 댓글, #31351, #31483, #31490)

Bun 1.4의 기술적 정체성은 "Rust 순혈주의"보다 "검증 가능한 경계와 빠른 커널"에 더 가깝습니다.

05.

사용자가 체감하는 결말: "I can't tell a difference"

Rust 포트를 처음 실제로 써 본 곳은 Claude Code였어요.

Jarred는 5월 21일에 "Been running Claude Code on the Rust port of Bun for the last few days. I can't tell a difference"라고 썼습니다.

이어서 전체 테스트가 모든 플랫폼에서 통과했고, 진짜 Rust 빌드를 쓰고 있는지 Claude한테 바이너리를 검사해 달라고 해야 했다고 덧붙였어요. 6월 4일에는 내부 Claude Code에 Rust 포트를 깐 지 하루가 지났는데 문제 보고가 하나도 없다고 했고요. (X , 후속, 내부 롤아웃)

"otherwise, barely anyone noticed. Boring is good." (bun-in-rust#production)

차이를 모르겠다는 말은 재작성 프로젝트에서 거의 합격 통보예요.

Claude Code는 v2.1.181부터 Rust 포트를 썼고 Linux 시작 시간이 10% 빨라졌는데, 대부분 눈치채지 못했습니다. Prisma Compute도 VM을 멈췄다가 다시 켠 뒤 연결 풀이 복구되지 않던 문제와 메모리 누수를 같은 식으로 시험했고, Rust 포트가 제대로 처리했다고 밝혔어요.

새 언어가 앞에 나서는 대신 기존 제품이 더 조용해진 셈이에요. (bun-in-rust#production)

숫자로 보면 이래요.

Claude Code 운영 CPU는 Bun 1.3에서 1.4로 오면서 p99가 24%에서 10%로, p50이 5.8%에서 2.5%로 내려갔습니다. hello-world 서버의 유휴 CPU는 5배 줄었어요. (cpu-usage )

HTTP 서버 peak RSS(Bun 1.3 → 1.4)는 fastify 233MB → 120MB(−48%), Express 169 → 92(−46%), node:http 135 → 81(−40%), Next.js 397 → 285(−28%), Bun.serve 45 → 36(−20%), Vite 268 → 233(−13%). 1.3 대비 13~48% 줄었어요. 원문 그래프의 막대 길이는 1.4 값의 상대 비율이에요. (memory-usage)

같은 60-module 프로젝트를 한 프로세스 안에서 2,000번 빌드하면 Bun 1.3.14는 6,745MB, 1.4.0은 609MB였습니다. 중간값도 있어요.

500회 1,914MB 대 526MB, 1,000회 3,506MB 대 586MB, 1,500회 5,097MB 대 608MB. 1.3.14는 호출마다 3MB쯤 새고 있었어요.

Next.js App Router SSR도 4,000페이지 뒤 238MB에서 멈춥니다(같은 조건에서 Node는 410MB).

운영하는 사람한테는 "최대 35% 감소"보다 곡선이 더는 오르지 않는다는 사실이 더 중요하죠. (memory leak, memory-usage)

hello.js 시작 시간은 Linux에서 1.3 10.9ms, 1.4 5.1ms였고 Windows에서 1.3 39.0ms, 1.4 15.5ms였어요.

peak memory는 Linux 33.0 → 14.6MB, Windows 46.5 → 16.8MB입니다. 이번 언어 교체는 성능 회귀를 감수하는 안정성 투자에 머물지 않았고, 안정성과 자원 효율이 같이 올라갔어요. (startup )

06.

bun install의 두 번째 재작성

Bun 1.4의 패키지 매니저 얘기는 벤치마크에서 이긴 것만이 아니고, 빠르기만 하던 도구가 팀 도구로 바뀐 얘기예요. 그래도 벤치마크부터 볼게요. T3-stack Next.js 예제(직접 의존성 25개, lockfile 약 220개 패키지) 기준입니다. (bun-install)

cold cache 첫 설치는 bun 1.4가 1.41s에 376MB, npm 18.1s에 503MB, pnpm 13.5s에 1.8GB, yarn 20.5s에 498MB.warm cache 새 checkout은 bun 251ms에 52MB, npm 7.61s에 798MB, pnpm 2.38s에 1.1GB, yarn 1.83s에 204MB.

아무것도 안 바뀐 재설치는 bun 12ms에 12MB, npm 337ms에 114MB, pnpm 400ms에 141MB, yarn 211ms에 107MB.

더 큰 변화는 PR #38333이었어요.

제목에 있는 그대로 dedupe, prune, pm licenses, audit fix, --filter, --catalog, nested override, transitive update, workspace 수정이 한 번에 들어갔습니다. GitHub 집계로 파일 390개, +107,650 / −7,700, 인라인 리뷰 65개.

본문에는 새로 쓰거나 옮겨 온 테스트 약 1,100개, 그리고 update scenario family 26개에서 pnpm과 결과를 맞춰 본 differential testing이 적혀 있어요. 목표는 Jarred가 직접 적었습니다.

"Brings Bun's package manager to parity with pnpm for monorepo workflows".

성능도 같은 PR 안에서 손봤어요. isolated linker는 항목 하나가 끝날 때마다 store entry 전체를 훑었는데, 이걸 entry마다 대기 목록을 다는 per-entry intrusive waiter list로 바꿨습니다.

2,259-entry 재현에서 main-thread link CPU가 0.85초에서 0.33초로, 6,425-entry 합성 monorepo에서는 5.5초에서 0.85초로 줄었어요. 대신 no-op isolated install은 링크를 다시 확인하느라 1.9ms(3.9%) 느려졌습니다.

Jarred가 "the removed work grows quadratically"라고 쓴 이유와 "Everything added is otherwise off the plain-install path"라고 방어한 이유가 이 숫자에서 같이 보여요.

기능 목록보다 이 리뷰 대화가 Bun 팀의 1.4 기준선을 잘 보여줘요.

I think we can keep it at v2 because we haven't shipped v2 in a prod release

Dylan Conway가 lockfile 버전을 두고 한 말입니다(리뷰).

다른 PR(#36360)에서는 이런 말이 오갔어요.

Do not leave open as follow ups. Do it here. In this PR.

what are some untested edgecases? add more tests.

(#36360 댓글 1, 댓글 2)

호환성 작업을 다음으로 미루지 않았고, 모노레포의 root, member, catalog 조합까지 지금 PR에서 끝내게 했습니다.

패키지 매니저의 신뢰는 "성공 메시지가 진실인가"에서 결정됩니다. PR #35426이 고친 건, 같은 저장소를 쓰는 git dependency N개를 설치하면 실제로는 하나만 깔고 "N packages installed"를 찍은 뒤 exit 0으로 끝나는 문제였습니다. 다시 돌릴 때마다 대략 하나씩 살아나는 이상한 실패였어요.

공유 clone 작업의 callback queue를 끝까지 비우고, 늦게 온 중복 요청도 이미 끝난 checkout으로 처리하게 고쳤습니다. 빠른 설치보다 잘못된 성공을 없애는 일이 더 우선이라는 좋은 사례예요.

07.

Node.js 호환성이 매 커밋 점수판이 됐어요

Bun은 "대부분의 npm 패키지가 돌아간다"는 경험담만으로 Node 호환성을 말하지 않았어요. Bun 1.4에서 Node.js 자체 테스트 1,517개가 새로 통과했고, 이 테스트는 커밋마다 돕니다.

node:http, node:fs, node:cluster, node:timers, node:zlib, node:vm, node:stream은 Node 테스트의 97% 수준이고, node:quic은 99%, node:events와 node:trace_events, node:sqlite는 100%예요. (node-js-compatibility)

Node.js v26 test suite 기준 모듈별 숫자입니다.

trace_events 30/30, sqlite 18/18 파일, quic 235/237, fs 349/358, http 403/415, vm 98/101.stream 254/262, cluster 85/88, http2 261/280, tls 191/224, worker 116/157, diagnostics 36/69.

가장 먼 쪽은 test_runner 28/81, inspector 22/110입니다.

node:sqlite는 파일 18개, subtest 319개에서 실패 0, skip 4입니다. (node-sqlite)

node:http는 원래 fetch 위에 얹은 shim이었어요. 이걸 버리고 Node의 _http_client.js를 직접 옮겨 와서 net/tls socket, Node HTTP parser, Agent pool 위에서 돌게 했습니다. 관련 커밋 설명에는 Node HTTP suite 통과율이 약 55%에서 82.3%로 오르고 vendored test 182개가 늘었다고 적혀 있어요. (commit c4a937c)

새 모듈도 API 모양만 맞춘 게 아니고 테스트로 확인한 구현입니다. node:vm은 72%에서 97%로, worker_threads는 약 37%에서 73.9%로 올랐습니다.

Playwright, Next.js 16, Vitest coverage, OpenTelemetry, Datadog profiling이 Bun에서 돌아간다는 결과는 이 점수판이 실제 생태계 호환성으로 이어졌다는 걸 보여줘요. (node-vm , worker-threads, playwright, next-js-16, vitest)

릴리스 노트는 100% 호환이라고 부풀리지 않아요. inspector 22/110, test runner 28/81, diagnostics_channel 36/69를 잘 된 모듈과 같은 표에 둡니다.

덕분에 내 스택에 필요한 모듈이 어디까지 왔는지 직접 볼 수 있어요.

08.

프런트엔드 파이프라인: stream, build, test

웹 앱 입장에서 제일 직접적인 변화는 Web Streams가 native 구현으로 바뀐 거예요.

64MB를 4KB chunk로 옮기는 download pipeline이 Bun 1.4에서 1,519MB/s, Deno 2.9에서 530MB/s, Node 26에서 204MB/s였습니다. subprocess pipeline은 Bun 1.3의 505MB/s에서 751MB/s로 올랐고, peak RSS는 207MB에서 65MB로 내려갔어요. 같은 조건에서 Node는 256MB/s에 106MB, Deno는 170MB/s에 114MB.

전부 같은 스크립트를 각 런타임에서 돌린 결과입니다. (streams-and-bodies)

Response.clone()도 프런트엔드 서버와 프록시 코드에 쓸모 있는 변화예요.

64MB streaming body를 양쪽에서 읽으면 Bun 1.4가 220MB에 96ms, Bun 1.3이 311MB에 129ms, Node 26이 382MB에 230ms. depth 100, body 10MB로 clone을 이어 붙이면 Bun 1.4는 약 20MB, Node와 Deno는 약 1,050MB예요. chunk를 branch마다 복사하지 않고 참조로 나눠 써서 그렇습니다. (streams-and-bodies , memory-and-reliability)

빌드는 클수록 차이가 벌어져요. 20,000-module diamond DAG의 code splitting은 Bun 1.3이 4.65s, 1.4가 320ms로 14배입니다. 컴포넌트 약 860개에 React Compiler를 돌리면 Bun 내장이 465ms(+71ms), Babel plugin이 9.15s. 전체 --compile은 기존 경로 13.04s 대비 3.62s예요.

React Compiler가 Bun parser 안에서 돌아서 Babel이나 SWC로 parse하고 다시 print하는 왕복이 없어졌고, 시간이 거기서 크게 빠졌습니다. (code-splitting, react-compiler)

bun test는 같은 24개 파일이 직렬로 9.4s, --parallel=4로 2.4s(3.9배).sql/postgres.test.ts(1.8s) 같은 느린 파일을 먼저 배치하고, worker가 비는 즉시 다음 파일을 가져가면서 coverage와 JUnit을 합칩니다.

--changed는 git diff에서 import graph를 거꾸로 따라가 영향받는 테스트만 골라요. 파일 단위 병렬 실행, 격리, shard, 과거 timing으로 나누기, 변경 영향 분석이 다 한 도구 안에 있습니다.

worker 수만 늘린 게 아니고 스케줄링과 리포트 병합까지 해요. (bun-test-parallel, timings, changed)

09.

마지막 장면: 의존성을 없애는 런타임

새 API 목록은 처음 보면 좀 산만해 보일 수 있어요. Bun.Image, Bun.WebView, Bun.markdown, Bun.cron, Bun.Terminal, 그리고 JSON5, JSONL, JSONC, XML, TOML, Archive, ANSI 문자열 유틸리티까지 한꺼번에 나왔거든요. 공통점은 있어요.

프로젝트를 시작할 때마다 깔던 작은 도구와 native addon을 런타임의 검증된 기본 기능으로 넣었습니다. (what-s-new)

Bun 자체 인터랙티브 데모는 package.json에서 dependency 15개를 하나씩 지우고 "0 dependencies left"로 끝나요.

지워지는 쪽은 sharp, puppeteer, marked, node-cron, node-pty, concurrently, npm-run-all, serve-static, json5, fast-xml-parser, tar, string-width, slice-ansi, cli-truncate, wrap-ansi이고, 들어오는 쪽은 Bun.Image, Bun.WebView, Bun.markdown, Bun.cron, Bun.Terminal, bun run --parallel, Bun.serve routes, Bun.JSON5, Bun.XML, Bun.Archive, Bun.stringWidth, Bun.sliceAnsi, Bun.wrapAnsi입니다.

프런트엔드 개발자에게 가장 상징적인 건 Bun.WebView예요.

macOS에서는 시스템 WebKit을 쓰고, macOS와 Linux, Windows에서는 설치된 Chrome, Chromium, Edge를 CDP로 조종합니다. 예제는 navigate, click, evaluate, screenshot을 await 5번으로 끝내고, click 이벤트는 isTrusted === true예요.

Puppeteer나 Playwright를 지우라는 얘기로 읽기보다는, 브라우저 자동화가 런타임 기본 기능이 됐다는 데 의미가 있다고 봐요. (bun-webview)

Bun.Image는 sharp와 비슷한 API로 JPEG, PNG, WebP, GIF, BMP를 다루고, native addon 설치가 필요 없어요.

1080p PNG를 400×400 JPEG로 바꾸면 sharp보다 1.38배, JPEG를 WebP로 바꾸면 1.19배 빠르다고 합니다. Bun.XML은 약 200KB feed에서 fast-xml-parser와 xml2js보다 5배쯤 빠르고, Bun.wrapAnsi는 JavaScript 구현보다 최대 88배, Bun.stringWidth는 CJK 텍스트에서 7~56배 빨라졌어요. (bun-image, bun-xml, wrapansi, stringwidth)

위험 표시도 같이 적혀 있습니다. Bun.markdown의 HTML 출력은 sanitize되지 않고, HTTP/3는 아직 production 사용을 권하지 않아요. 빠른 기본값과 이런 위험 표시가 같이 있어야 이 전략이 설득력을 얻는다고 생각해요. (bun-markdown , http-3)

그렇다고 npm 패키지가 필요 없어진 건 아니에요. Bun이 런타임, 패키지 매니저, bundler, test runner, 브라우저 자동화, 이미지와 문서 처리를 한 배포 단위 안으로 모으면서, 들여다보고 최적화할 수 있는 경로가 늘어난 겁니다. (what-s-new )

"One engineer can do a lot more today than a year ago." (bun-in-rust#what-s-next)

이 이야기를 "AI가 백만 줄을 썼다"로 끝내면 중요한 절반을 잃어버려요. 실제로 일어난 일은 엔지니어 한 명이 테스트 체계, 역할 분리, 컴파일러, sanitizer, fuzzing, production canary를 엮어서, 전에는 엄두도 못 냈을 전환을 통제해낸 거였어요.

제품을 만든 건 생성 속도가 아니었어요. 검증 구조였습니다. (what-s-next, the-work-continues, production)

10.

재미 포인트: 본문 사이에 숨어 있던 이야기 14개

  1. 1.

    Rust 포트가 너무 티가 안 나서 바이너리를 검사했어요. Jarred는 Claude Code를 며칠 돌리고 "I can't tell a difference"라고 했고, 정말 Zig 버전이 아닌지 Claude한테 바이너리 검사까지 시켰습니다. (X, 후속)

  2. 2.

    백만 줄 PR에 달린 댓글이 "테스트부터 의심하라"였어요. make sure Claude didn't change the tests to "fix" the problem. 이 프로젝트의 신뢰 문제가 이 한 문장에 다 있습니다. (bun-in-rust)

  3. 3.

    Claude들이 서로 git stash와 reset --hard를 날렸어요. 병렬화가 처음 무너진 곳은 모델 성능이 아니라 공유 작업 디렉터리와 Git 규칙이었습니다. (false-starts)

  4. 3.5.

    같은 싸움이 그래프에서는 안 일어나요. omo의 dag는 쓰기 범위가 겹치지 않는 곳에만 병렬 레인을 허용합니다. 노드 프롬프트는 각자 혼자 서야 하고, dependsOn은 순서만 정할 뿐 데이터를 나르지 않아요.

  5. 4.

    코드는 분당 약 1,300줄 쓰였는데 "아무것도 동작하지 않았다". 블로그가 속도 자랑 바로 뒤에 이 얘기를 붙여요. 저는 그 편집이 좋았습니다. (another-false-start)

  6. 5.

    "긴 설명 주석이 필요하면 코드가 틀린 것." Claude가 stub과 장문의 변명 주석을 만들자 Jarred는 리뷰 규칙을 바꿨어요. (even-more-false-starts)

  7. 6.

    Windows가 마지막으로 초록불이 됐어요. Buildkite에서 플랫폼별로 빨강과 초록이 엎치락뒤치락하다가 build #54202에서 끝났습니다. (CI)

  8. 7.

    테스트가 너무 거칠어서 격리 머신이 여러 번 죽었어요. TCP socket 고갈, GB 단위 disk I/O, process spawn 약 10,000개짜리 테스트가 있었거든요. (CI)

  9. 8.

    성공했다고 찍고 하나만 설치한 패키지 매니저. 같은 git repo를 쓰는 dependency N개 중 하나만 깔고 N packages installed에 exit 0. (#35426)

  10. 9.

    패치 파일은 앞 16KiB만 같으면 같은 걸로 쳤어요. PR #32749가 cache identity를 patch 전체의 SHA-1로 바꿔서 이 충돌을 막았습니다. (#32749)

  11. 10.

    링커 order file이 망가져도 빌드는 성공했어요. 컴파일 오류가 나는 대신 Linux RSS 약 12MB가 조용히 되돌아왔습니다. (#33302)

  12. 11.

    Rust로 바꾸자마자 hot path를 C++ SIMD로 옮겼어요. "Rust rewrite"라고 해서 모든 코드를 Rust로 옮기진 않았어요. (#31491)

  13. 12.

    1.4 출시를 한때 막은 주요 blocker는 영상이었어요. Rust rewrite가 들어간 릴리스의 main blocker가 영상 녹화라고 Jarred가 직접 썼습니다. (X)

  14. 13.

    제일 좋은 production 평가는 지루함이었어요. Claude Code Linux startup이 빨라졌는데 대부분 몰랐고, Jarred는 "Boring is good."이라고 썼습니다. (production)

11.

메이킹: 이 글은 노드 8개짜리 에이전트 그래프로 조립했어요

이 보고서는 DAG 실행 dag_f27ee8d6, 노드 8개짜리 그래프가 조립했어요. 블로그 요약, 저장소 통계, PR 이야기, X 포스트, 스타일 패턴을 모으는 레인 다섯 개가 한 번에 같이 돌았고, 한국어로 합치는 노드를 거쳐 visual-engineering 빌드 노드와 기계적 검증 노드로 갔습니다.

펼쳤다가 모으고, 검증해요. Bun 팀이 워크플로 약 50개와 피크 64개 병렬 에이전트로 11일 동안 부딪히며 찾은 모양과 같아요. 다른 점은 하나, 여기서는 그 모양을 처음부터 정해 놓고 시작했다는 것뿐입니다.

중간에 프로바이더가 끊기는 일도 있었습니다.

그래도 다시 짓지 않았고 새 실행 키도 받지 않았어요. 깨진 노드만 amend와 자식 되살리기로 그 자리에서 고쳤고, 이미 끝난 노드는 캐시된 결과를 썼습니다.

재미 포인트 3번의 git stash와 reset --hard 싸움은 쓰기 범위가 겹치지 않는 그래프에서는 일어날 수가 없어요.

글도 같은 식으로 다듬었어요. 존댓말 톤 교정은 문서를 다섯 조각으로 잘라 병렬로 돌렸고, 각 조각은 태그 개수 검사를 통과해야 합쳐졌습니다.

먼저 쪼개고 나서 나눠 보냈어요. 질문은 어떤 모델이 좋으냐가 아니었어요. 더 작은 레인으로 어떻게 나누느냐였습니다.

결과는 검증 전까지 주장이라는 규칙도 이 글에 똑같이 걸었어요. 영어판은 체크리스트 루프 아래에서 독립된 두 번째 모델한테 넘어갔고, 첫 심사는 FAIL이었습니다.

실패한 항목만 고쳐서 다시 심사받고 나서야 SHIP이 나왔어요. "make sure Claude didn't change the tests"랑 같은 얘기예요.

그 FAIL 판정이 내 글을 내가 채점하지 않았다는 기록으로 남아 있습니다.

반복할 수 있는 건 모델이 아니고 그래프 엔지니어링이에요. 혼자 실패할 수 있는 레인, 주장으로 다루는 결과, 깨진 곳만 고치는 복구.mass-ulw는 이걸 그래프로, hyperplan은 논증으로, start-work는 원장으로 담았습니다. 한 회사가 11일 동안 부딪혀 찾은 작업 방식이, 이제는 저녁 한나절에 선언하는 그래프 하나로 재현되는 이유예요.

그래프 전체는 오픈소스입니다. github.com/code-yeongyu/oh-my-openagent에서 같은 파이프라인을 돌려 볼 수 있어요.

← 글 목록