
Microservices chậm hơn Monolith bao nhiêu? Con số thực nghiệm khiến kỹ sư bất ngờ
Wotech
Tác giả tại Wotech Electronics Blog
Microservices được ca ngợi là kiến trúc "hiện đại", nhưng bạn có bao giờ tự hỏi: đổi lấy sự linh hoạt đó, hệ thống phải trả giá bao nhiêu về hiệu năng? Một thí nghiệm vừa được thực hiện đã đưa ra con số cụ thể — và nó lớn hơn nhiều người tưởng.
Tranh luận giữa Monolith và Microservices chưa bao giờ ngừng trong giới kỹ thuật. Ai cũng biết lý thuyết: chuyển sang Microservices đồng nghĩa với đánh đổi một phần hiệu năng lấy sự linh hoạt khi mở rộng quy mô đội ngũ. Nhưng "một phần" là bao nhiêu? 5%? 10%? Thực tế, rất ít bài kiểm thử nào đo lường sát số liệu. Bài viết này tổng hợp một thực nghiệm chi tiết và khách quan do kỹ sư Arjay McCandless (nguyên kỹ sư kiến trúc Microservices và Serverless tại Amazon) thực hiện, được anh Lữ Minh Khương chia sẻ và phân tích trên nhóm VGU Tech Alumni.
Kết quả gây sốc trong một đoạn
Khi tách một ứng dụng Monolith chạy trên máy chủ 8 vCPU (16GB RAM) thành 4 Microservices chạy trên 4 máy chủ 2 vCPU (4GB RAM mỗi máy) — với cùng tổng ngân sách phần cứng 112 USD/tháng — sức chịu tải tối đa của hệ thống:
- Giảm từ 60.000 người dùng (khoảng 15.400 requests/giây) xuống còn 18.000 người dùng nếu dùng HTTP/JSON
- Đạt 19.000 người dùng nếu dùng gRPC
- Ngay cả khi tối ưu phân bổ tài nguyên (rebalancing replicas) và dùng gRPC, cấu hình Microservices tốt nhất cũng chỉ đạt 30.000 người dùng — tức giảm 50% hiệu năng so với Monolith trên một máy chủ lớn, và giảm 40% so với Monolith nhân bản ngang trên 4 máy chủ nhỏ (50.000 người dùng)
Nguyên nhân cốt lõi: chi phí CPU cho việc đóng gói/giải mã dữ liệu (encoding/decoding), quản lý kết nối mạng, và việc phải thay thế các câu lệnh SQL JOIN trực tiếp bằng chuỗi lời gọi API qua lại giữa các service.
Phương pháp kiểm thử: công bằng đến từng chi tiết
Để phép thử công bằng, tác giả sử dụng Conduit — một nền tảng blog tương tự Medium, thuộc chuẩn benchmark RealWorld. Người dùng có thể đăng bài, theo dõi nhau, tải bảng tin, yêu thích bài viết, bình luận và quản lý hồ sơ.
Cả hai phiên bản đều được viết bằng Go với thư viện HTTP tiêu chuẩn:
- Monolith: toàn bộ ứng dụng chạy trong một tiến trình duy nhất.
- Microservices: tách thành 4 service độc lập — Users, Profiles, Articles, Comments.
Về cơ sở dữ liệu, mỗi service nắm quyền sở hữu các bảng riêng: Users quản lý thông tin người dùng, Profiles quản lý quan hệ theo dõi, Articles quản lý bài viết/tags/favorites, Comments quản lý bình luận. Cả 4 service dùng chung một cụm PostgreSQL 16 vCPU, nhưng mỗi service kết nối qua một database role riêng và chỉ truy cập đúng bảng của mình — đúng nguyên tắc cô lập dữ liệu (data ownership) của Microservices.
Đây chính là điểm thay đổi hoàn toàn cách truy xuất dữ liệu: trong Monolith, lấy bài viết kèm tên tác giả chỉ cần một câu SQL JOIN. Trong Microservices, Articles service không thể JOIN trực tiếp bảng của Users hay Profiles — mà phải gọi API sang các service tương ứng.
Đã tối ưu "sạch sẽ" trước khi đo
Để không ai đổ lỗi cho "thiết kế ngây thơ", bản Microservices được tối ưu kỹ:
- Internal API hỗ trợ gom nhóm theo danh sách ID (batching) — tránh lỗi N+1 calls kinh điển
- Tái sử dụng kết nối (connection pooling) giữa các request
- Xác thực token xử lý cục bộ, không tốn request gọi về Auth service
- Các service giao tiếp qua mạng nội bộ cùng trung tâm dữ liệu (New York), loại bỏ độ trễ liên vùng
- Không dùng API proxy hay Service Mesh — không lớp trung gian nào thêm overhead
- Mọi cấu hình phải vượt qua 197 bài kiểm tra tương thích (conformance checks) trước khi đo
Cấu hình phần cứng và tiêu chí chấm điểm
Ngân sách tầng Application Server cố định 112 USD/tháng trên DigitalOcean (tổng chi phí thực tế cho cả cụm database và máy tạo tải lên tới hơn 1.500 USD/tháng). Với mức ngân sách này, có 2 lựa chọn tương đương:
| Lựa chọn | Cấu hình |
|---|---|
| 1 | Một máy chủ lớn 8 vCPU, 16GB RAM |
| 2 | Bốn máy chủ nhỏ, mỗi máy 2 vCPU, 4GB RAM |
Phía trước là Nginx chạy trên máy 8 vCPU riêng; tải được bắn từ 3 máy load generator độc lập. Cứ 1.000 người dùng ảo tạo khoảng 250–270 requests/giây với tác vụ hỗn hợp: lướt bài, mở profile, xem bình luận, thỉnh thoảng đăng nội dung.
Điểm số của mỗi cấu hình = số người dùng tối đa mà hệ thống chịu liên tục trong 5 phút mà vẫn đạt SLA:
- 95% request (p95) phản hồi dưới 500ms
- 99% request (p99) phản hồi dưới 1 giây
- Tỷ lệ lỗi dưới 1%
Kết quả thực nghiệm qua từng giai đoạn
Giai đoạn 1: Thiết lập mức cơ sở với Monolith
Chạy Monolith trên máy chủ lớn 8 vCPU/16GB: hệ thống chịu tải 60.000 người dùng (~15.400 req/s).
Tiếp theo, tác giả nhân bản ngang Monolith (horizontally scaled monolith): 4 bản sao giống hệt nhau trên 4 máy 2 vCPU, có Nginx cân bằng tải. Kết quả: 50.000 người dùng — giảm khoảng 17% so với một máy lớn, do overhead hệ điều hành riêng trên từng máy, overhead mạng và sự không đồng nhất của phần cứng ảo hóa.
Vậy mốc cơ sở của Monolith: 60.000 người dùng (1 máy lớn) và 50.000 người dùng (4 máy nhỏ).
Giai đoạn 2: Tách 4 service nhưng vẫn chạy trên cùng một máy 8 vCPU
Để cô lập biến số phần cứng và chỉ đo chi phí thuần túy của kiến trúc phần mềm, cả 4 service chạy thành 4 tiến trình riêng trên cùng máy 8 vCPU.
- HTTP/JSON: sức tải tụt từ 60.000 xuống 25.000 người dùng
- gRPC (Protocol Buffers): đạt 29.000 người dùng
Đáng chú ý nhất là số liệu CPU: mỗi public request, Monolith chỉ tốn 0,47ms CPU, trong khi Microservices HTTP tốn tới 1,2ms CPU — khối lượng công việc CPU tăng hơn 2,5 lần, ngay cả khi mọi thứ nằm trên cùng một máy, không đi qua cáp mạng vật lý nào.
Lý do: các lời gọi hàm trong bộ nhớ (in-process function calls) giờ trở thành lời gọi qua tiến trình mạng nội bộ. Để trả về một bài viết, Articles service gọi Profiles service, Profiles lại gọi Users service — mỗi bước đều tốn client code, server code, quản lý kết nối, encode/decode JSON và lập lịch của hệ điều hành.
Giai đoạn 3: Triển khai 4 service trên 4 máy chủ riêng
Đây là mô hình sát thực tế triển khai nhất — mỗi service một máy 2 vCPU.
- HTTP/JSON: chỉ chịu được 18.000 người dùng
- gRPC: chịu được 19.000 người dùng
Đặt cạnh con số 50.000 người dùng của Monolith nhân bản trên đúng 4 máy này, sự sụt giảm là cực lớn.
Khi kiểm tra CPU utilization để tìm nghẽn cổ chai, tác giả phát hiện mất cân bằng tải nghiêm trọng: máy chạy Articles bị đẩy lên 95% CPU, trong khi máy Comments chỉ dùng hết 52%. Việc chia đều "mỗi service một máy" giả định sai rằng mọi service cần tài nguyên như nhau — trong khi thực tế lưu lượng đọc bài viết nặng hơn hẳn bình luận. CPU dư ở máy Comments không thể "chuyển" sang giúp Articles.
Giai đoạn 4: Tối ưu phân bổ tài nguyên (Rebalancing)
Tận dụng ưu điểm mở rộng độc lập của Microservices mà không tăng ngân sách (vẫn 4 máy 2 vCPU), tác giả triển khai thêm bản sao thứ hai của Articles service lên máy đang chạy Comments, và điều chỉnh lại tỷ lệ phân phối lưu lượng:
- HTTP/JSON: tăng nhẹ từ 18.000 lên 20.000 người dùng (1,27ms CPU/request)
- gRPC: tăng vọt từ 19.000 lên 30.000 người dùng (0,91ms CPU/request — cải thiện ~58% so với trước cân bằng, giảm 28% chi phí CPU so với HTTP)
30.000 người dùng trên 4 máy nhỏ đã tiệm cận kết quả Microservices trên 1 máy lớn (29.000), nhưng vẫn thấp hơn 40% so với Monolith trên 4 máy nhỏ (50.000) và thấp hơn 50% so với Monolith trên 1 máy lớn (60.000).
Bảng tổng hợp kết quả
| Cấu hình | Phần cứng | Giao thức nội bộ | Sức tải (người dùng) | CPU/request |
|---|---|---|---|---|
| Monolith | 1 máy 8 vCPU | — (in-process) | 60.000 | 0,47ms |
| Monolith nhân bản x4 | 4 máy 2 vCPU | — (in-process) | 50.000 | ~0,47ms |
| Microservices | 1 máy 8 vCPU | HTTP/JSON | 25.000 | 1,20ms |
| Microservices | 1 máy 8 vCPU | gRPC | 29.000 | 1,01ms |
| Microservices | 4 máy 2 vCPU | HTTP/JSON | 18.000 | — |
| Microservices | 4 máy 2 vCPU | gRPC | 19.000 | — |
| Microservices (rebalanced) | 4 máy 2 vCPU | HTTP/JSON | 20.000 | 1,27ms |
| Microservices (rebalanced) | 4 máy 2 vCPU | gRPC | 30.000 | 0,91ms |
Serverless và Auto-scaling có cứu được không?
Nhiều người nghĩ tới Serverless (AWS API Gateway + Lambda + DynamoDB) hoặc auto-scaled containers để tự bù phần compute thiếu. Nhưng số liệu thực nghiệm cho thấy: tự động cấp phát tài nguyên không làm biến mất chi phí xử lý phụ trội. Để phục vụ 15.000 public requests/giây (mức tải mà Monolith gánh được):
| Phiên bản | CPU/request | Cores cần (100%) | Cores cần (ngưỡng 80%) |
|---|---|---|---|
| Monolith | 0,47ms | 7 | 9 |
| Microservices gRPC (balanced) | 0,91ms | 14 | 17 |
| Microservices HTTP (balanced) | 1,27ms | 19 | 24 |
Nói cách khác: dù có Serverless hay auto-scaling, bạn vẫn phải trả tiền cho lượng compute gấp 2 đến gần 3 lần để xử lý cùng một lưu lượng.
Vậy tại sao Amazon, Netflix, Facebook vẫn chọn Microservices?
Câu trả lời không nằm ở hiệu năng máy chủ, mà ở quy mô con người và tổ chức (organizational scaling).
Khi có hàng trăm kỹ sư chia thành các đội phụ trách Search, Billing, Recommendation... việc có ranh giới service rõ ràng giúp từng đội tự build, test, deploy, rollback độc lập — không phải chờ chu kỳ release chung. Mỗi đội còn có thể chọn ngôn ngữ/framework phù hợp nhất bài toán của mình. Với tổ chức lớn, tốc độ phát triển tính năng và sự tự chủ của đội ngũ hoàn toàn xứng đáng với chi phí hạ tầng tăng thêm.
Hai cái bẫy nhiều dự án mắc phải
- Sự độc lập giả tạo (Distributed Monolith): nhiều hệ thống tách Microservices nhưng cứ ra mắt tính năng mới lại cần 4 đội phối hợp sửa API cùng lúc, deploy theo đúng thứ tự, quản lý phiên bản phức tạp. Kết quả: mất hiệu năng của Monolith mà không có sự độc lập của Microservices — mất cả hai thế giới.
- Ảo tưởng cô lập lỗi (Blast radius isolation): lý thuyết một service lỗi không kéo sập toàn hệ thống. Nhưng nếu không thiết kế chịu lỗi (fault tolerance, graceful degradation), một service cốt lõi như Users gặp sự cố vẫn kéo sập mọi tính năng của các service phụ thuộc nó.
Góc nhìn cho doanh nghiệp Việt Nam
Với đa số doanh nghiệp vừa và nhỏ tại Việt Nam — đội ngũ kỹ thuật vài chục người, ngân sách hạ tầng có hạn — bài học rút ra khá rõ ràng:
- Đừng tách Microservices vì "thời đại". Nếu hệ thống của bạn chưa chạm ngưỡng mà một máy chủ mạnh (hoặc vài bản sao Monolith sau load balancer) không gánh nổi, Monolith gần như luôn là lựa chọn hiệu quả hơn về chi phí.
- Nếu đã tách: chú ý cân bằng tài nguyên theo lưu lượng thực tế (không chia đều máy cho mỗi service), ưu tiên gRPC cho giao tiếp nội bộ, dùng batching và caching hợp lý.
- Modular Monolith — chia module và ranh giới code rõ ràng bên trong một ứng dụng — rất có thể trở lại là lựa chọn mặc định cho đa số dự án mới, đặc biệt khi các công cụ AI giúp đội ngũ nhỏ làm được khối lượng công việc của đội lớn, giữ vận hành đơn giản và tránh toàn bộ chi phí giao tiếp nội bộ không cần thiết.
Kiến trúc nào cũng có cái giá của nó. Điều quan trọng là biết rõ mình đang trả giá cho gì — và bài thực nghiệm trên cho thấy cái giá đó đo đếm được chính xác đến từng millisecond CPU.
Kết luận
Microservices không "xấu" — nó là công cụ giải bài toán tổ chức, không phải bài toán hiệu năng. Nếu mục tiêu của bạn là tối ưu chi phí hạ tầng cho một lượng traffic xác định, Monolith (đơn hoặc nhân bản ngang) thắng áp đảo: cùng 112 USD/tháng, bạn nhận gấp đôi sức chịu tải. Nếu mục tiêu là để nhiều đội phát triển song song, độc lập ở quy mô hàng trăm kỹ sư — Microservices đáng giá từng đồng phụ phí.
Bạn đang vận hành hệ thống theo kiến trúc nào? Đã từng "gồng mình" tối ưu độ trễ và chi phí hạ tầng sau khi tách Microservices chưa? Chia sẻ trải nghiệm ở phần bình luận.
Nguồn tham khảo
- Thực nghiệm gốc: Arjay McCandless — "How much slower are Microservices? (I tested)"
- Phân tích tiếng Việt: Lữ Minh Khương — nhóm VGU Tech Alumni
- Chuẩn benchmark ứng dụng: RealWorld (Conduit)