Bun vs Node.js 프로덕션 2026: 6개월 마이그레이션 후 솔직한 결론

작년 9월부터 올해 2월까지, 약 6개월에 걸쳐 회사 API 서버를 Node.js에서 Bun으로 마이그레이션했다. 팀은 3명이고, 서비스는 B2B SaaS — 일일 활성 사용자 약 2만 명, 엔드포인트 60여 개짜리 Express 기반 서버다. 최대한 정직하게 썼다.

미리 말해두면: 결론이 깔끔하지 않다. “Bun이 좋아, 무조건 써” 혹은 “Node.js가 여전히 답이야”로 끝나는 글이 아니다. 실제로 해보니 상황이 그보다 훨씬 복잡했다.


왜 Node.js 23이 있는데도 Bun을 시도했나

솔직히 말하면, 2025년 초까지만 해도 “Bun은 벤치마크용 장난감 아닌가?” 하는 생각이 있었다. Node.js 22에서 --experimental-strip-types 플래그가 생기면서 TypeScript를 바로 실행할 수 있게 됐고, “Bun의 킬러 피처가 사라지는 거 아닌가?” 싶었다.

그런데 Bun 1.2가 나오면서 흐름이 좀 달라졌다. 패키지 매니저 속도, 번들러 통합, 네이티브 SQLite 지원 같은 것들이 개발 흐름에 실질적인 영향을 주기 시작했다. 내가 마이그레이션을 결심한 건 성능 벤치마크 때문이 아니라, CI에서 직접 bun install을 돌려본 뒤였다.

# Node.js 23 + npm (패키지 1,847개 기준)
npm install: 47초 (캐시 없음), 12초 (캐시 있음)

# Bun 1.2.x
bun install: 4.2초 (캐시 없음), 1.1초 (캐시 있음)

이게 하루에 수십 번 돌아가는 CI에서 누적되면 상당한 차이다. 빌드 비용도 문제지만, 피드백 루프가 짧아지는 게 팀 생산성에 직접적으로 연결된다. 그게 시작이었다.


런타임 벤치마크: 수치는 맞지만 맥락이 중요하다

인터넷에 떠도는 “Bun이 Node.js보다 3배 빠르다” 같은 수치는 대부분 hello-world HTTP 서버 기준이다. 실제 비즈니스 로직이 붙으면 차이가 줄어든다. 그래서 내 서버에서 직접 측정했다.

테스트 환경: AWS EC2 t3.medium, 10만 건 요청, 동시 접속 100
엔드포인트: POST /api/reports (PostgreSQL 쿼리 3개, Redis 캐시 1개 포함)

Node.js 23.5.0
  평균 응답시간: 38ms  |  P99: 94ms  |  처리량: 2,340 req/s

Bun 1.2.4
  평균 응답시간: 29ms  |  P99: 71ms  |  처리량: 3,180 req/s

약 35% 향상. 무시할 수 없는 수치다. 그런데 이 결과를 처음 봤을 때 좀 의아했다. DB 쿼리가 포함된 엔드포인트에서 이만큼 차이가 나려면 런타임 자체 성능 외에 다른 이유가 있을 텐데, 라고 생각했다. I/O 병목이 있으면 런타임 효율이 희석되는 게 보통이니까.

파고들어 보니 — Bun의 fetch 구현과 HTTP 서버 내부가 V8/libuv 스택보다 시스템 콜을 덜 한다는 게 핵심이었다. 특히 짧은 커넥션이 많은 워크로드에서 이 차이가 벌어진다. 우리 API 패턴이 딱 그랬다. 요청 하나당 DB를 치고 바로 응답하는 방식.

메모리는 별개 이야기다:

Node.js 23: 아이들 상태 ~145MB, 피크 ~380MB
Bun 1.2.4:  아이들 상태 ~89MB, 피크 ~270MB

Lambda처럼 메모리 비용이 직접 청구되는 환경에서는 이게 꽤 의미 있다. 우리는 Lambda 함수 몇 개를 256MB에서 128MB로 줄였다.


마이그레이션 중 실제로 깨진 것들 — 금요일 오후에 배포한 이야기 포함

자, 여기부터가 진짜다.

express는 생각보다 잘 동작했다. Bun이 Node.js API를 거의 다 폴리필하기 때문에 대부분의 Express 미들웨어는 그냥 올라갔다. 문제는 예상치 못한 곳에서 터졌다.

첫 번째 사고: node-gyp 기반 네이티브 모듈. 우리가 쓰던 bcrypt가 네이티브 바인딩을 사용하는데, Bun에서 이게 동작하지 않았다. bcryptjs로 교체하는 건 어렵지 않았는데 — 문제는 타이밍이었다. 금요일 오후 4시에 스테이징에 배포했다가 로그인이 전부 깨지는 걸 발견했다. 그냥, 왜 하필 금요일에.

두 번째 사고: winstonDailyRotateFile transport가 간헐적으로 로그 로테이션에 실패했다. Bun에서 파일 시스템 이벤트 처리 방식이 달라서인지 — 정확한 원인은 100% 확신하지 못한다. GitHub 이슈를 뒤져보니 Bun 1.1.x 때 보고된 버그였고 1.2.x에서 수정됐다고 나왔는데, 실제로는 여전히 재현됐다. 결국 pino로 바꿨고, 오히려 더 좋아졌다. 구조적 로깅으로 가는 계기가 됐다고 긍정적으로 생각하기로 했다.

세 번째: 이건 진짜 예상 못 했는데 — cluster 모듈 지원이 제한적이다. 우리는 CPU 코어를 최대한 쓰려고 Node.js cluster 기반으로 워커를 띄우는 구조였는데, Bun에서는 이게 동작하지 않았다. Bun 공식 문서에서는 자체 worker_threads 구현이나 Bun.serve()의 멀티코어 지원으로 대체하라고 하는데, 리팩터링에 일주일이 걸렸다.

그런데 — 결과적으로 더 나은 구조가 됐다. Bun.serve()의 내장 멀티코어 처리가 내가 직접 짠 cluster 코드보다 깔끔했다. 어쩌면 이건 마이그레이션이 강제해준 정리였다.


Bun이 예상외로 실망스러운 곳

빛나는 부분은 이미 여러 곳에서 다뤘으니 여기서는 솔직하게 아쉬운 부분을 먼저 쓴다.

에러 메시지가 불친절하다. Node.js에 비해 런타임 에러 스택 트레이스가 덜 유용한 경우가 있었다. 특히 네이티브 모듈 관련 에러는 “왜 이게 안 되는지” 파악하는 데 시간이 더 걸렸다. 익숙해지면 나아지는 문제이긴 한데, 초반에는 디버깅 비용이 올라갔다.

Windows 지원이 아직 불안정하다. 우리 팀원 한 명이 Windows를 쓰는데, Bun 1.2.x에서도 파일 경로 처리 쪽에서 간헐적으로 문제가 생겼다. “WSL에서 돌려”가 해결책이 되긴 하지만, 좋은 해답은 아니다. 팀이 더 커지면 이 부분이 진짜 병목이 될 수 있다.

API 정체성 혼란. 이건 사소해 보여도 실제로 온보딩에서 문제가 됐다. Bun이 Node.js 생태계를 끌어안으려다 보니 “Bun 방식”과 “Node.js 호환 방식”이 공존한다. 공식 문서도 두 가지를 같이 설명하다 보니 처음 온보딩하는 팀원이 어떤 걸 써야 할지 헷갈려했다. 작은 불편함이지만 쌓인다.

반대로 예상보다 훨씬 좋았던 것:

Jest (Node.js) — 테스트 427개: 43초
bun test          — 동일 테스트:  8.7초

테스트 실행이 5배 빠르면 더 자주 돌리게 된다. 아주 단순한 이야기인데 실제로 그렇게 됐고, 팀 전체의 TDD 습관이 좀 달라졌다. 억지로 바꾼 게 아니라 — 돌리는 게 아깝지 않으니까 그냥 자연스럽게 됐다.

그리고 bun:sqlite. 가벼운 설정 저장용으로 SQLite를 쓰는 부분이 있었는데, better-sqlite3 패키지 대신 네이티브 모듈을 쓰니 의존성이 하나 줄고 빌드가 간단해졌다. 작은 거지만 이런 것들이 쌓이면 유지보수 부담이 줄어든다.


지금 실제로 어떻게 쓰고 있나

6개월 지난 지금, 스택 상황은 이렇다.

메인 API 서버는 Bun으로 완전히 이전했고, 프로덕션에서 안정적으로 돌아가고 있다. Lambda 함수들도 Bun으로 바꿨다. 콜드 스타트가 확실히 빠르다.

Node.js 23 Lambda 콜드 스타트 (256MB): 평균 340ms
Bun 1.2.4 Lambda 콜드 스타트 (256MB): 평균 180ms

반면 레거시 내부 도구 두 개는 아직 Node.js다. node-gyp 의존성이 깊이 박혀 있고, 건드렸다가 다른 게 또 깨질 것 같아서 — 지금은 그냥 두기로 했다. 리소스 대비 얻는 게 적다.

결론을 말하자면 이렇다. 새 프로젝트라면 망설이지 않고 Bun으로 시작하겠다 — 서버리스 환경, TypeScript 기반 서비스, CI 속도가 병목인 상황이라면 특히. 기존 서비스 마이그레이션은 node-gyp 의존성부터 확인해야 한다. 거기가 실질적인 마이그레이션 비용의 대부분이 숨어 있다. Windows 개발 환경이 팀에 섞여 있거나 cluster 기반 아키텍처가 깊이 박혀 있다면 — 일정에 여유를 많이 잡아야 한다.

막연히 “빠르다니까”로 덤비면 금요일 오후에 내가 겪은 일을 똑같이 당하게 된다. 마이그레이션 전에 Bun GitHub 이슈 트래커를 한 번 훑어보는 것만으로도 몇 시간의 디버깅을 아낄 수 있다. 내가 겪은 문제 절반은 이미 알려진 이슈였다.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top