Socket.IO 서버 벤치마킹
Source: Dev.to
코드만 조금만 바꾸면 네 가지 다른 Socket.IO 서버를 만들 수 있습니다 – 그리고 기본 서버를 사용하는 것은 절대 원하지 않을 겁니다.
저는 런타임(Bun, Node)과 WebSocket 서버(ws, uWebSockets.js, bun‑engine)의 조합을 비교해서 부하가 걸렸을 때 어떻게 동작하는지 살펴볼 것입니다.
이 서버들을 Socket.IO와 함께 사용하는 방법은 공식 문서를 참고하세요.
설정
경쟁자
| 라벨 | 런타임 | WebSocket 서버 |
|---|---|---|
| node‑ws | Node.js 24.11.1 | ws |
| node‑uws | Node.js 24.11.1 | uWebSockets.js v20.52.0 |
| bun‑ws | Bun 1.3.6 | ws |
| bun‑native | Bun 1.3.6 | @socket.io/bun-engine 0.1.0 |
*ws*는 기본값입니다. 순수 JS이며 신뢰할 수 있지만 빠르지는 않다 (스포일러: 그렇습니다).
테스트 서버는 최근 프로젝트 **Versus Type**의 백엔드를 약간 수정한 버전이며, 실시간 PvP 타이핑 게임입니다. 인증, 속도 제한, DB 호출을 제거했습니다.
부하 생성기에는 **Artillery**와 artillery-engine-socketio-v3 플러그인을 사용해 수천 명의 동시 클라이언트가 WebSocket으로 연결하고 게임을 플레이하도록 시뮬레이션합니다.
하드웨어
| 역할 | 인스턴스 | 사양 |
|---|---|---|
| Server | AWS Standard B2als v2 | 2 vCPU, 4 GB RAM, Ubuntu 22.04 LTS |
| Attacker | AWS Standard B4als v2 | 4 vCPU, 8 GB RAM, Ubuntu 22.04 LTS |
공격 흐름
- Artillery는 초당 4개의 가상 사용자를 생성합니다.
- 각 사용자는 **
/api/pvp/matchmake**를 호출합니다. - 서버는 매치메이킹 알고리즘을 실행하고 룸 ID를 반환합니다 (룸당 최대 6명).
- 사용자는 WebSocket을 통해 연결하고, 룸에 참여하여 게임 상태(텍스트)를 받습니다.
- 서버는 카운트다운을 방송하고, 플레이어는 0이 될 때까지 기다립니다.
- 사용자는 분당 60타 (60 WPM) 속도로 키 입력 이벤트를 전송합니다 (≈ 1 이벤트 / 200 ms).
- 각 키 입력마다 서버는 입력을 검증하고, 상태를 업데이트하며, 새로운 상태를 룸 내 모든 사람에게 방송합니다.
- 사용자는 지연 시간을 추적하기 위해 매초 ping 이벤트를 보냅니다.
텍스트는 충분히 길어 벤치마크가 끝나기 전에 게임이 종료되지 않도록 합니다.
(실제 서버는 또한 매초 시스템 메시지와 WPM 업데이트를 방송합니다.)
GitHub 저장소: (서버, 클라이언트 및 결과 데이터를 포함합니다).
Source: …
결과
전체 승자
Node + uWS (파란선)은 메모리 사용량을 제외한 모든 지표에서 다른 모든 구성보다 뛰어났으며, 메모리 사용량에서는 Bun이 1위를 차지했습니다.
0 – 800 클라이언트
- Bun 서버는 이벤트‑루프 지연이 ~0 ms 로 Node 서버에 비해 현저히 낮습니다.
node‑uws가 가장 안정적입니다.ws서버(Bun 및 Node 모두) 는 p95 지연이 15‑20 ms 로 상승합니다.- 나머지 두 구성은 ≈ 5 ms 수준을 유지하며 견고합니다.
800 – 1 500 클라이언트
node‑ws가 급격히 폭발— 1 k 클라이언트 부근에서 지연이 급증합니다.bun‑ws와bun‑native도 뒤따르지만, 발생 시점이 늦습니다.- 이벤트‑루프 지연도 동일한 패턴을 보입니다.
- CPU 사용량은
node‑ws가 ~1 k 클라이언트에서 100 %,bun‑ws가 ~1.2 k,bun‑native가 ~1.3 k 에서 **100 %**에 도달합니다. node‑uws는 ≈ 80 % CPU 를 유지하며, 1.5 k 클라이언트에서도 다른 구성과 비슷한 상승률을 보입니다.- 처리량은
node‑uws를 제외한 모든 구성에서 불안정해집니다. - 메모리:
node‑uws가 이상하게 감소했다가 다시 상승하는 모습을 보이며, Bun 서버는 전체적으로 메모리 사용량이 적음 – Bun 의 메모리 관리가 인상적입니다.
핵심: node‑ws 는 부하를 감당하지 못하고, node‑uws 는 평탄한 지연과 낮은 이벤트‑루프 지연을 유지합니다.
1 500 – 2 100 클라이언트
node‑ws,bun‑ws,bun‑native는 사실상 죽은 상태이며 지연이 천정부지로 상승합니다.node‑uws는 여전히 ≈ 80 % CPU 를 유지하면서 낮은 지연을 보입니다.- 참고:
node‑ws의 p95 지연이 잠시 평탄해 보이는 이유는 메트릭 기록이 중단돼 Artillery 의 pushgateway 가 새로운 값이 들어올 때까지 마지막 값을 그대로 보여주기 때문입니다.
2 100 – 3 300 클라이언트
node‑uws가 꾸준히 버텨냅니다 – CPU 가 80 % 수준을 유지하고, 지연도 낮으며 서버가 응답성을 유지합니다.- 다른 모든 구성은 압도당해 CPU 가 100 % 로 치솟고, 지연이 크게 폭발하며 빈번히 충돌합니다.
주요 내용
| 구성 | 강점 | 약점 |
|---|---|---|
| node‑uws | 전반적으로 최고의 성능, 안정적인 지연 시간, 낮은 이벤트‑루프 지연, ~80 % CPU로 3 k 이상 클라이언트를 처리 | Bun보다 약간 높은 메모리 사용량 |
| bun‑native | 뛰어난 메모리 사용량, 낮은 클라이언트 수에서 낮은 이벤트‑루프 지연 | 높은 클라이언트 수를 유지할 수 없으며, ~1.3 k 클라이언트 이후 지연 시간이 급격히 상승 |
| bun‑ws | 초기 단계에서 낮은 이벤트‑루프 지연, 좋은 메모리 사용량 | 부하가 걸리면 bun‑native보다 빨리 실패 |
| node‑ws | 간단하고 기본 옵션 | 약 1 k 클라이언트에서 충돌, 높은 CPU 사용량 및 지연 시간 |
결론: 고트래픽 실시간 애플리케이션에 원시 성능이 필요하다면 Node + uWebSockets.js가 명확한 승자입니다. 메모리가 주요 고려 사항이고 Bun에 익숙하다면 Bun 네이티브 엔진이 견고한 2차 선택이 될 수 있지만, 확장성 한계에 주의해야 합니다.

`node-uws` is the only one still standing. It's at ~90‑100 % CPU now.
Throughput starts to become less stable, and latency slowly creeps up. It goes dead after ~3 250 clients.
We can say it could handle a solid 3 000‑3 100 concurrent clients just fine—more than double the next best (`bun-native`).
전체 그래프
CSV 파일은 GitHub에서 여기 확인할 수 있습니다.
Bun, 무슨 일이 있었나요?
bun-native가 여기서 완전히 망가지는 것을 보는 것은 놀라운 일입니다, 왜냐하면 Bun WebSocket 서버가 내부적으로 uWebSockets를 사용하기 때문입니다.
정확한 이유는 잘 모르겠지만, @socket.io/bun-engine이 아직 매우 새롭고 (v0.1.0) 비효율성 및 오버헤드를 추가하는 추상화 레이어가 있을 수 있기 때문일지도 모릅니다.




