Socket.IO 서버 벤치마킹

발행: (2026년 1월 20일 오전 12:59 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

Source: https://example.com/your-article

설정

경쟁자

라벨런타임WebSocket 서버
node‑wsNode.js 24.11.1ws
node‑uwsNode.js 24.11.1uWebSockets.js v20.52.0
bun‑wsBun 1.3.6ws
bun‑nativeBun 1.3.6@socket.io/bun-engine 0.1.0

*ws*는 기본값입니다. 순수 JS이며 안정적이지만 빠르지는 않습니다(스포일러: 빠르지 않음).

테스트 서버는 최근 프로젝트 Versus Type 의 백엔드를 약간 수정한 버전입니다—실시간 PvP 타이핑 게임. 인증, 레이트‑리밋, DB 호출을 제거했습니다.

로드 제너레이터로는 Artilleryartillery-engine-socketio-v3 플러그인을 사용해 수천 명의 동시 클라이언트가 WebSocket으로 연결해 게임을 플레이하도록 시뮬레이션합니다.

하드웨어

역할인스턴스사양
서버AWS Standard B2als v22 vCPU, 4 GB RAM, Ubuntu 22.04 LTS
공격자AWS Standard B4als v24 vCPU, 8 GB RAM, Ubuntu 22.04 LTS

공격 흐름

  1. Artillery가 초당 4명의 가상 사용자를 생성합니다.
  2. 각 사용자는 /api/pvp/matchmake 를 호출합니다.
  3. 서버는 매치메이킹 알고리즘을 실행하고 룸 ID를 반환합니다(룸당 최대 6명).
  4. 사용자는 WebSocket으로 연결해 방에 입장하고 게임 상태(텍스트)를 받습니다.
  5. 서버가 카운트다운을 방송하고, 플레이어들은 0이 될 때까지 대기합니다.
  6. 사용자는 분당 60타(≈ 1 event / 200 ms) 속도로 키 입력 이벤트를 전송합니다.
  7. 키 입력마다 서버는 입력을 검증하고, 상태를 업데이트한 뒤 방에 있는 모든 사람에게 새로운 상태를 방송합니다.
  8. 사용자는 지연 시간을 측정하기 위해 1초마다 ping 이벤트를 보냅니다.

텍스트는 벤치마크가 끝날 때까지 게임이 종료되지 않도록 충분히 길게 설정했습니다.
(실제 서버는 또한 시스템 메시지와 매초 WPM 업데이트를 방송합니다.)

GitHub 저장소: (서버, 클라이언트 및 결과 데이터 포함).

Source:

결과

전체 승자

Node + uWS (파란선) 가 모든 지표에서 메모리 사용량을 제외하고 다른 모든 구성보다 우수했으며, 메모리 사용량에서는 Bun이 앞섰다.

0 – 800 클라이언트

0‑800 Graph

  • Bun 서버는 이벤트 루프 지연(~0 ms) 로 Node 서버에 비해 현저히 낮다.
  • node‑uws 가 가장 안정적이다.
  • ws 서버들 (Bun 및 Node 모두) 은 p95 지연이 15‑20 ms 로 상승한다.
  • 나머지 두 구성은 ≈ 5 ms 로 견고하게 유지된다.

800 – 1 500 클라이언트

800‑1500 Graph

  • node‑ws 가 폭발적으로 지연이 증가한다 – 약 1 k 클라이언트에서 급증.
  • bun‑wsbun‑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 클라이언트

1500‑2100 Graph

  • node‑ws, bun‑ws, bun‑native 는 사실상 죽은 상태가 되며 – 지연이 천정부지로 상승한다.
  • node‑uws 는 여전히 ≈ 80 % CPU 로 일정한 낮은 지연을 유지한다.
  • 참고: node‑ws 의 p95 지연이 잠시 평탄하게 보이는 이유는 메트릭 기록이 중단돼서이며, Artillery 의 pushgateway 가 새로운 값이 도착하기 전까지 마지막 값을 계속 보여준다.

2 100 – 3 300 클라이언트

2100‑3300 Graph

  • node‑uws꾸준히 버텨낸다 – CPU 가 80 % 수준을 유지하고 지연도 낮으며 서버가 응답성을 유지한다.
  • 다른 모든 구성은 압도당 – CPU 가 100 %에 달하고 지연이 크게 폭증하며 빈번히 충돌한다.

Source:

Takeaways

ConfigurationStrengthsWeaknesses
node‑uws전반적인 성능 최고, 안정적인 지연시간, 낮은 이벤트‑루프 지연, ~80 % CPU로 3 k 이상 클라이언트 처리 가능Bun보다 약간 높은 메모리 사용량
bun‑native메모리 사용량이 매우 적고, 클라이언트 수가 적을 때 낮은 이벤트‑루프 지연높은 클라이언트 수를 지속할 수 없으며, ~1.3 k 클라이언트 이후 지연시간이 급격히 상승
bun‑ws초기 단계에서 낮은 이벤트‑루프 지연, 좋은 메모리 사용량부하가 걸리면 bun‑native보다 일찍 실패
node‑ws간단하고 기본 옵션약 1 k 클라이언트에서 충돌, CPU와 지연시간이 높음

Bottom line: 고트래픽 실시간 애플리케이션에서 순수 성능이 필요하다면 Node + uWebSockets.js가 명확한 승자입니다. 메모리가 주요 고려사항이고 Bun에 익숙하다면 Bun 네이티브 엔진이 괜찮은 2차 선택이 될 수 있지만, 확장성 한계에 주의해야 합니다.

![Benchmark Graph](https://media2.dev.to/dynamic/image/width=800,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj46ux807ur388inzxs51.png)

`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`).

Full Graph

Full Graph

CSV 파일은 GitHub 여기에서 확인할 수 있습니다.

Bun, What Happened?

It’s a surprise to see bun-native get absolutely destroyed here, because the Bun WebSocket server uses uWebSockets under the hood.

I don’t exactly know the reason why, but it might be because @socket.io/bun-engine is still very new (v0.1.0) and may have inefficiencies and abstraction layers that add overhead.

Back to Blog

관련 글

더 보기 »

초보자를 위한 Pusher vs Socket.io 설명

실시간 기능에 대해 이야기할 때 “그냥 WebSockets를 사용해” 혹은 “Pusher가 더 쉽다” 같은 말을 들어본 적이 있을 겁니다. 처음에는 이 주제가 종종 …

고트래픽 Node.js API 최적화 전략

Node.js의 이벤트‑드리븐 아키텍처를 활용하세요. I/O 작업을 논블로킹으로 유지하고, 블로킹 코드 대신 async/await 또는 promises를 사용합니다. javascript // Use async…