Một server $12 có thể chịu tải bao nhiêu user tùy theo ngôn ngữ bạn sử dụng

 


[ Một server $12 có thể chịu tải bao nhiêu user tùy theo ngôn ngữ bạn sử dụng ]
TL;DR
Tác giả thử nghiệm 8 ngôn ngữ/framework backend (Node.js, Bun, Python FastAPI, PHP Laravel, C# ASP.NET Core, Java Spring Boot, Go, Rust Axum) trên cùng một VPS 12 USD (1 vCPU shared, 2GB RAM) chạy kèm PostgreSQL. Kết quả cho thấy sự chênh lệch rất lớn: Laravel dừng lại ở 750 concurrent users, trong khi Java gánh được 5.100, Go đạt 6.500 và Rust chạm mốc 6.900 users. Khi loại bỏ overhead network/process của PostgreSQL bằng cách chuyển sang SQLite (WAL mode), Java vượt 10.000 users, còn Rust chạm ngưỡng 14.050 users (~1.300 RPS) trên đúng 1 core CPU duy nhất.
==============================
Thử nghiệm benchmark 8 ngôn ngữ backend trên VPS 12 USD: Chênh lệch gần 10 lần và câu chuyện nút thắt cổ chai

Hôm nay mình vừa xem được một video thử nghiệm benchmark khá trực quan và thực tế từ kênh Arjay McCandless. Chủ đề không mới nhưng cách tiếp cận rất chuẩn chỉ: Liệu lựa chọn ngôn ngữ và framework backend thực sự ảnh hưởng bao nhiêu đến khả năng chịu tải của một server giá rẻ?


Tác giả dựng một máy ảo VPS thông số tối thiểu: 1 vCPU shared (2.3 GHz), 2GB RAM, 50GB SSD, chạy toàn bộ stack trên đó gồm Nginx, backend và PostgreSQL. Ứng dụng mô phỏng API mạng xã hội kiểu Twitter với 4 thao tác cơ bản: xem feed (lấy 20 bài viết kèm like count), xem chi tiết post, like bài viết và tạo bài mới. Database được seed sẵn 50.000 users, 500.000 posts và hơn 2 triệu lượt likes, có đầy đủ indexes.

Kịch bản test dùng K6 để giả lập Virtual Users (VU), mỗi user sinh ra khoảng 0.1 req/s (có độ trễ nghỉ giữa các thao tác). Điều kiện để vượt qua bài test là chạy xác nhận liên tục trong 5 phút mà không vi phạm 3 tiêu chí:
  • p95 latency dưới 500ms
  • p99 latency dưới 1.000ms
  • Tỷ lệ lỗi (error rate) dưới 1%
Tất cả các backend đều viết thuần query SQL, không dùng ORM, không cache tầng ứng dụng, cấu hình connection pool ở mức 10 connections và vượt qua bộ test kiểm tra tính toàn vẹn 41 test cases để đảm bảo trả về dữ liệu y hệt nhau.



Dưới đây là kết quả thực tế khi chạy với PostgreSQL:
  1. PHP (Laravel 13 + PHP-FPM): 750 users (~94 RPS).
    Đây là kết quả gây bất ngờ nhất vì con số quá thấp. Lý do nằm ở vòng đời request truyền thống của PHP-FPM: mỗi request đến lại boot toàn bộ framework Laravel rồi giải phóng. Với các API cực nhẹ, chi phí boot framework chiếm phần lớn CPU. Khi bật Laravel Octane để giữ app trên RAM, con số tăng lên 1.250 users. Thử nghiệm viết lại bằng PHP thuần (Bare PHP) không framework thì kéo lên được 2.700 users.
  2. Python (FastAPI + Uvicorn): 2.150 users (~200 RPS).
    Khi đẩy lên 2.300 users, hệ thống gãy không phải do CPU hay latency tăng dần, mà do connection pool bị nghẽn dẫn đến 15% request bị timeout sau 5 giây chờ connection.
  3. Node.js (Node 22 + Express 5): 3.250 users (~300 RPS).
    Chạy ở tiến trình đơn (single process), vượt qua bài test 5 phút ổn định với median latency khoảng 14ms.
  4. Bun: 4.200 users (~389 RPS).
    Giữ nguyên code JavaScript của Express và chỉ đổi runtime sang Bun, dung lượng tải tăng ngay ~30% so với Node.
  5. C# (ASP.NET Core): 4.400 users (~400 RPS).
    Hiệu năng rất tốt. Tuy nhiên, tác giả phát hiện driver Npgsql mặc định gửi lệnh DISCARD ALL khi reset connection trong pool, vô tình đẩy tải CPU của Postgres lên đến 60% trong khi app C# chỉ tốn tầm 25% CPU.
  6. Java (Java 21 + Spring Boot 3 MVC / Tomcat): 5.100 users (~500 RPS).
    Spring Boot hoạt động cực kỳ mượt mà, vượt hơn Node.js tận 57%. Điểm đánh đổi duy nhất của JVM là mức ngốn RAM khoảng 600MB trên con máy 2GB, nhưng không hề gây nghẽn hệ thống.
  7. Go (net/http chuẩn): 6.500 users (~600 RPS).
    Gấp đôi hiệu năng của Node.js. Ở mức này, CPU của app Go chỉ ăn khoảng 24%, còn 60% CPU đã dồn hết cho PostgreSQL.
  8. Rust (Axum + SQLx): 6.900 users (~640 RPS).
    Đứng đầu bảng với PostgreSQL, gấp hơn 9 lần Laravel mặc định. Dẫu vậy, Rust chỉ nhỉnh hơn Go tầm 6%, cho thấy cả hai đã bắt đầu chạm trần năng lực xử lý của database server nằm chung máy.
Khi gỡ bỏ nút thắt PostgreSQL bằng SQLite

Nhận thấy PostgreSQL đang là điểm nghẽn ngốn phần lớn CPU của con VPS 1 vCPU, tác giả quyết định lấy top 3 ngôn ngữ dẫn đầu (Java, Go, Rust) để test lại với SQLite ở chế độ WAL (Write-Ahead Logging) nhằm loại bỏ hoàn toàn chi phí network, IPC và context switch giữa 2 tiến trình:

  • Java (Spring Boot) + SQLite: Đạt 10.250 users (~950 RPS), tăng gấp đôi so với khi chạy Postgres.
  • Go + SQLite: Đạt 11.750 users (>1.000 RPS), tăng 80%.
  • Rust + SQLite: Đạt 14.050 users (~1.300 RPS), median latency 5ms, p99 chỉ 459ms.
Một con số thực sự ấn tượng cho một máy ảo giá rẻ chỉ có 1 core chia sẻ và 2GB RAM.

Bài test này mang lại hai góc nhìn rất rõ ràng cho anh em làm hệ thống:
Thứ nhất, việc chọn runtime/framework có ảnh hưởng trực tiếp đến năng lực chịu tải ban đầu của server (khoảng cách giữa 750 và 6.900 users là một khoảng cách khổng lồ về chi phí hạ tầng).
Thứ hai, khi tầng code đã đủ nhanh (như Go, Rust hay Java), nút thắt cổ chai sẽ lập tức dịch chuyển về phía database và I/O. Lúc đó, việc cố tối ưu thêm vài microsecond ở tầng code không còn ý nghĩa bằng việc tối ưu hóa truy vấn, connection pool hoặc kiến trúc lưu trữ.

Mọi người đánh giá thế nào về bảng kết quả này, và trong các dự án thực tế mọi người thường ưu tiên tối ưu framework hay mở rộng database trước?

Post a Comment

Mới hơn Cũ hơn