[ Thêm CPU có thực sự giúp hệ thống chịu tải tốt hơn? ]
TL;DR:
- Nâng cấp từ 1 vCPU lên 8 vCPU giúp tăng khả năng chịu tải (capacity) của ứng dụng từ 6.5 đến 7.2 lần ở cả 4 ngôn ngữ khi cấu hình tận dụng đa nhân hợp lý.
- Tại mốc 8 vCPU (chạy chung Nginx, Backend và PostgreSQL trên cùng một máy), sức chịu tải tối đa đạt được là: Go (22,000 users - ~5,600 RPS), Java Spring Boot (21,000 users - ~5,400 RPS), Rust Axum (19,000 users - ~5,000 RPS) và Python FastAPI (10,000 users - ~2,600 RPS).
- Lượng RAM tiêu thụ ở mốc 8 vCPU: Go dùng ít nhất (175 MB), Python tốn 900 MB (8 workers) và Java tốn nhiều nhất (3.6 GB với default heap settings).
- Với Go, Java và Rust, điểm nghẽn nằm ở Database khi PostgreSQL ngốn hơn 50% CPU toàn hệ thống (application chỉ chiếm 27% - 30% CPU). Ngược lại, bản thân tầng application của Python ngốn tới 55% CPU và chỉ đẩy được 31% tải sang PostgreSQL.
- Tốc độ trên từng vCPU của Cloud Provider (DigitalOcean) không đồng đều: máy 4 vCPU chậm hơn tới 20% trên mỗi core so với máy 1 vCPU, và việc nâng cấp máy to hơn không giúp chi phí trên mỗi người dùng (price per user) rẻ hơn.
=======================
Chào mọi người, anh chị em làm backend hay hạ tầng chắc hẳn đều từng thắc mắc: Nếu một ứng dụng đang chạy trên server 1 core CPU mà nâng lên 8 cores thì thực tế sẽ gánh thêm được bao nhiêu người dùng? Thay vì nói về lý thuyết, hôm nay mình chia sẻ lại với mọi người bài test thực nghiệm rất trực quan của tác giả Arjay McCandless. Anh ấy đã viết cùng một ứng dụng bằng Python, Java, Go và Rust, sau đó đem chạy thử tải trên các gói server 1, 2, 4 và 8 vCPUs.
1. Thiết lập bài test
Thử nghiệm được chạy trên các máy ảo DigitalOcean Premium AMD với cấu hình và chi phí tăng tuyến tính: 1 vCPU / 2 GB RAM (14 USD/tháng), 2 vCPUs / 4 GB RAM (28 USD), 4 vCPUs / 8 GB RAM (56 USD) và 8 vCPUs / 16 GB RAM (112 USD). Chỉ số CPU steal time được đo đạc gần như bằng 0, đảm bảo việc chia sẻ phần cứng trên cloud không làm sai lệch kết quả.
Trên mỗi server, cả Nginx, Backend và PostgreSQL đều chạy chung và chia sẻ tài nguyên CPU. Ứng dụng được test là Conduit (một bản clone của Medium) gồm 19 API endpoints với khoảng 2.3 GB dữ liệu có sẵn trong Database (100,000 users, 500,000 articles, 5,000,000 favorites, 2,500,000 comments và 2,000,000 follows). Các câu lệnh SQL đều được đánh index và giới hạn phân trang 100 comments mới nhất. Không ngôn ngữ nào dùng ORM hay application cache.
Tác giả giả lập hành vi người dùng thật (đọc bài, lướt feed, bình luận...) với tần suất trung bình 1 request mỗi 4 giây cho một người dùng (5% là write requests), tức 1,000 users tương đương 250 - 270 RPS. Điểm số của mỗi server là lượng users tối đa duy trì liên tục trong 5 phút mà vẫn đảm bảo SLA: 95% requests dưới 0.5 giây, 99% dưới 1 giây và tỷ lệ lỗi dưới 1%.
2. Kết quả thử nghiệm trên từng ngôn ngữ
- Python (FastAPI + Uvicorn): Do giới hạn của Global Interpreter Lock (GIL), Python phải chạy nhiều worker processes để tận dụng đa nhân. Khi cấu hình 1 worker/core, sức chịu tải tăng từ 1,400 users (1 core) lên 2,500 (2 cores), 4,100 (4 cores) và đạt 10,000 users (~2,600 RPS) ở 8 cores (tăng hơn 7 lần). Thử nghiệm tăng số workers theo công thức (2 x cores + 1) tức 17 workers trên 8 cores không giúp tăng tải (chỉ đạt 9,300 users) mà còn làm RAM tăng từ 900 MB lên 1.4 GB do chi phí quản lý processes.
- Java (Spring Boot + Virtual Threads): Tận dụng Virtual Threads để lập lịch các request nhẹ nhàng trên OS threads mà không cần native compilation. Kết quả tăng từ 2,900 users (1 core) lên 5,000 (2 cores), 9,300 (4 cores) và đạt 21,000 users (~5,400 RPS) ở 8 cores (tăng 7.2 lần). Điểm trừ duy nhất là với default heap settings, JVM dùng tới 3.6 GB RAM ở mốc 8 cores.
- Go (Standard HTTP Library + pgx): Dùng Goroutines phân phối tải trực tiếp trong một process duy nhất. Khả năng chịu tải đi từ 3,200 users (1 core) lên 4,900 (2 cores), 9,800 (4 cores) và dẫn đầu ở mốc 8 cores với 22,000 users (~5,600 RPS), tăng khoảng 7 lần. Ấn tượng nhất là tiến trình Go chỉ tốn vỏn vẹn 175 MB RAM trên máy 8 cores.
- Rust (Axum + Tokio Multi-threaded Runtime): Tách riêng tác vụ nặng như password hashing ra blocking pool để không làm nghẽn async tasks. Sức chịu tải đạt 2,900 users (1 core), 4,900 (2 cores), vươn lên dẫn đầu ở mốc 4 cores với 10,000 users và đạt 19,000 users (gần 5,000 RPS) ở 8 cores (tăng 6.5 lần).
Nhìn chung, Go, Java và Rust có hiệu năng rất sát nhau và cùng nằm chung một phân khúc, sự chênh lệch nhỏ này không đủ để xếp hạng tuyệt đối ngôn ngữ nào mạnh hơn.
3. Những phát hiện thú vị đằng sau các con số
- Nút thắt thực sự nằm ở Database: Trên máy 8 cores, với Go, Java và Rust, bản thân ứng dụng chỉ dùng 27% - 30% CPU, Nginx chiếm 11% - 12%, trong khi PostgreSQL ngốn hơn 50% tổng CPU để xử lý truy vấn. Dù bạn đổi ngôn ngữ nào thì Database vẫn phải làm chừng đó việc. Riêng với Python, tầng application ngốn tới 55% CPU và PostgreSQL chỉ chạy hết 31%, cho thấy chi phí xử lý của bản thân ngôn ngữ Python lớn hơn rất nhiều.
- Sự thiếu đồng đều của vCPU trên Cloud: Bạn có thể thấy bước nhảy từ 1 lên 2 và 4 cores khá thấp, nhưng từ 4 lên 8 cores lại tăng vọt. Khi benchmark riêng CPU, tác giả phát hiện dù cùng gắn nhãn Premium AMD nhưng tốc độ mỗi core trên máy 2 cores chậm hơn 7%, và trên máy 4 cores chậm hơn tới 20% so với máy 1 core (4 vCPUs thực chất chỉ mạnh gấp 3.2 lần). Đến máy 8 cores thì tốc độ mỗi core mới quay lại ngang bằng với máy 1 core.
- Bài toán chi phí (Price per user): Việc nâng từ 1 lên 8 cores giúp tổng tải tăng khoảng 7 lần, nhưng giá thuê server lại đắt gấp 8 lần (từ 14 USD lên 112 USD). Tính trên ứng dụng Go, máy 1 core phục vụ được 229 users trên mỗi 1 USD/tháng, trong khi máy 8 cores chỉ đạt 196 users trên mỗi 1 USD/tháng (do thêm chi phí điều phối đa nhân).
Nâng cấp server nhiều cores chắc chắn giúp hệ thống gánh được tổng lượng truy cập lớn hơn nhiều lần, nhưng không đồng nghĩa với việc tối ưu hơn về mặt chi phí trên từng người dùng. Anh chị em trong quá trình vận hành hệ thống có hay gặp tình trạng nâng cấp vCPU trên Cloud mà hiệu năng tăng không như kỳ vọng không, cùng chia sẻ thêm ở phần bình luận nhé!








Đăng nhận xét