uWebSockets.js vs ws vs Socket.IO: Benchmark 10.000+ kết nối và RAM thực tế
So sánh uWebSockets.js, ws và Socket.IO bằng benchmark 10.000+ WebSocket connections: phương pháp test, server mẫu, RAM/RSS, throughput, latency, backpressure và cách chọn.
ShanverseUpdated 10 min read— viewsLoading views
Ba đối thủ không hoàn toàn cùng tầng
Trước khi đo, cần hiểu ta đang so sánh gì:
uWebSockets.js và ws có thể được benchmark ở raw WebSocket semantics. Socket.IO không phải drop-in WebSocket server: raw browser WebSocket client không nói được Socket.IO protocol. So sánh đúng phải có hai nhóm:
Raw transport: uWebSockets.js vs ws với cùng message bytes.
uWebSockets.js thường là ứng viên mạnh nhất khi mục tiêu là số connection/core và throughput tối đa.
ws thường là điểm cân bằng tốt giữa hiệu năng, độ đơn giản và tính tương thích Node.js.
Socket.IO tối ưu time-to-market và tính năng ứng dụng; overhead đổi lại reconnect, rooms, event model và middleware.
Bottleneck thực tế có thể nằm ở JSON, database, Redis, authorization, TLS hoặc load generator—không phải WebSocket library.
Đây là giả thuyết cần xác minh, không phải số liệu universal.
Vì sao không nên chép một bảng số “thực tế” từ Internet?
RAM và throughput thay đổi mạnh theo:
CPU architecture, số core và clock.
Linux kernel, ulimit, NIC và network path.
Phiên bản Node.js, V8 và library.
TLS, compression và payload size.
Idle, echo hay fan-out broadcast.
Message rate mỗi client.
Backpressure và client đọc nhanh/chậm.
Object/session giữ trên mỗi connection.
Load generator có bị CPU-bound hay không.
Tài liệu Socket.IO công khai benchmark tới 10.000 clients trên Ubuntu 22.04, Node.js 20.3.0 với Socket.IO 4.7.2, uWebSockets.js 20.33.0 và ws 8.11.0; chính tài liệu cũng nhấn mạnh memory tăng gần tuyến tính theo số client. Đây là reference hữu ích nhưng version/hardware phải được ghi lại khi áp dụng.
Benchmark matrix cho 10.000+ kết nối
Không đo một scenario duy nhất. Chạy ít nhất bốn workload:
Profile chuẩn đề xuất
Sau baseline, chạy riêng:
TLS on.
permessage-deflate on với cấu hình giống nhau.
Production auth/session middleware.
Payload và message rate gần traffic thật.
Kiến trúc test công bằng
Mermaid
Loading diagram…
Không đặt 10.000 clients và server trên cùng một Node.js process. Tốt nhất:
Server ở một máy/VM riêng.
Ít nhất hai load generator để xác minh client không bị bottleneck.
Cùng AZ/LAN cho benchmark runtime; chạy WAN riêng nếu muốn đo network latency.
Pin CPU/cgroup giống nhau cho mọi server.
Restart host/container giữa các candidate.
Warm-up trước khi lấy mẫu.
Randomize thứ tự run để giảm bias nhiệt/caching.
Chuẩn bị Linux
Kiểm tra file descriptor:
Cho môi trường benchmark có kiểm soát:
ip_local_port_range đặc biệt quan trọng trên load generator. Một source IP bị giới hạn bởi ephemeral ports; muốn vượt cao có thể cần nhiều source IP/máy.
Ghi lại:
Không thay sysctl production chỉ để “đẹp benchmark” nếu chưa hiểu ảnh hưởng vận hành.
Ba server phải có cùng semantics
1. ws echo server
ws có optional bufferutil để tăng tốc masking/unmasking. Nếu bật, phải bật và ghi rõ thành một candidate riêng; đừng để máy này có addon còn máy khác không.
2. uWebSockets.js echo server
Pin đúng git tag/commit của uWebSockets.js vì dự án được phân phối khác package npm thông thường. Ghi lại commit trong report.
Nếu production cần HTTP long-polling fallback, chạy thêm candidate Socket.IO default transports thay vì đánh đồng với WebSocket-only.
Đừng benchmark ba implementation khác payload
Raw WebSocket gửi 128 byte nhưng Socket.IO gửi object JSON có metadata thì không còn apples-to-apples.
Hai bài test hợp lệ:
Transport cost
Cùng application payload bytes.
Không business validation.
Không room lookup.
Không database/broker.
Compression off.
Application cost
Cùng feature outcome.
Auth giống nhau.
Cùng room size/fan-out.
Cùng acknowledgement/retry policy.
Cùng serialization và broker.
Cần publish cả hai; transport benchmark cho biết trần kỹ thuật, application benchmark cho biết capacity thực.
Metrics phải thu thập
Connection
Attempted, established và failed connections.
Handshake p50/p95/p99.
Unexpected disconnects.
Reconnect rate.
Message
Sent/received messages per second.
Successful echo/fan-out rate.
p50, p95 và p99 latency.
Lost, duplicate và out-of-order application events.
Server
CPU user/system.
RSS, heap used, external và array buffers.
Event-loop utilization/lag.
File descriptors và socket states.
Network RX/TX và retransmits.
GC pause frequency/duration.
Backpressure/buffered bytes.
Đo RAM đúng cách
heapUsed không phải tổng RAM. Native addon, Buffer, TLS và kernel socket buffers có thể nằm ngoài V8 heap. Tối thiểu ghi:
Ở OS/cgroup:
Công thức memory tăng thêm trên mỗi connection:
Báo cả median và peak RSS. Đợi connection ổn định, chạy GC tự nhiên và không dùng một snapshot ngay sau ramp-up làm kết luận.
Bảng kết quả nên publish
Không điền số từ máy khác. Report của bạn nên có dạng:
Mỗi ô là median của ít nhất 5 run; p99 nên tính từ HDR histogram hoặc histogram có độ phân giải phù hợp, không chỉ từ average.
Cách đọc kết quả
uWebSockets.js thắng raw benchmark
Đây là kết quả thường được kỳ vọng do core native tối ưu và data path ngắn. Nhưng cần kiểm tra:
Có giữ được lợi thế khi thêm auth, JSON, DB và broker?
Team có vận hành native addon/version pin ổn định?
Backpressure và graceful shutdown có đúng?
Feature cần có phải tự xây bao nhiêu?
ws chậm hơn nhưng vẫn dư capacity
Nếu một process ws giữ 10k connection với p99/SLO đạt yêu cầu và CPU còn headroom, chuyển sang uWS chỉ để thắng benchmark có thể không đáng migration risk.
Điều này phản ánh Engine.IO/Socket.IO session, event packet, rooms và feature state. Câu hỏi đúng không phải “tại sao raw WS nhẹ hơn?” mà là:
Chi phí hạ tầng tăng thêm có nhỏ hơn chi phí tự xây reconnect, rooms, acknowledgement và client compatibility không?
Load generator đạt 100% CPU trước server
Kết quả không đo server. Scale generator ngang, giảm client-side metrics hoặc dùng generator native hiệu quả hơn. Theo dõi CPU hai phía trong mọi run.
Compression có thể đảo kết quả RAM
ws ghi rõ permessage-deflate tạo overhead đáng kể về performance và memory, và compression concurrency trên Linux có thể dẫn tới memory fragmentation. Vì vậy:
Baseline compression off.
Chỉ bật khi bandwidth saving đáng kể.
Dùng threshold để không nén payload nhỏ.
Giới hạn concurrency/context takeover.
Chạy soak test hàng giờ, không chỉ 60 giây.
Socket.IO/Engine.IO và uWebSockets.js cũng cần cấu hình compression tương đương; “default khác nhau” không phải benchmark công bằng.
Backpressure: nơi benchmark đẹp thường che giấu lỗi
10.000 client nhanh không giống 9.999 client nhanh + 1 client rất chậm.
Mỗi implementation phải có policy:
Max buffered bytes per socket.
Drop/coalesce cho event “latest state wins”.
Disconnect slow consumer khi vượt ngưỡng.
Durable replay cho event không được mất.
Metric về queue depth và dropped messages.
Một server có throughput rất cao bằng cách queue vô hạn trong RAM không phải server nhanh; đó là memory leak có độ trễ.
Tối ưu RAM cho từng lựa chọn
uWebSockets.js
Dùng built-in topic pub/sub thay vì giữ Set lớn khi phù hợp.
Giới hạn maxBackpressure và maxPayloadLength.
Không attach object graph lớn vào ws user data.
Theo dõi native/RSS, không chỉ V8 heap.
ws
Tắt perMessageDeflate nếu chưa benchmark.
Đặt maxPayload.
Kiểm tra bufferedAmount/backpressure trước broadcast.
Đánh giá bufferutil như candidate riêng.
Cleanup timer/listener và dead connection bằng ping/pong.
1uWebSockets.js → tối đa hóa transport efficiency2ws → raw WebSocket cân bằng và dễ tích hợp3Socket.IO → trả overhead để lấy feature và developer velocity
Hướng dẫn chi tiết cách tải và đưa bộ kỹ năng lập trình chuyên nghiệp của Matt Pocock vào giao diện Claude.ai Web/Desktop thông qua Skills Settings và Project Knowledge.
Hướng dẫn từng bước cài đặt và cấu hình bộ Agent Skills của Matt Pocock cho Claude Code CLI trên Linux và Windows. Tự động hóa quy trình TDD, refactor code và quản lý task dự án.
Hướng dẫn cài đặt PostgreSQL trên Ubuntu chi tiết từ A-Z: cài đặt, tạo database và user, cấu hình remote access, UFW, backup, restore và xử lý lỗi thường gặp.