Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là kiến trúc sư hệ thống của một sàn thương mại điện tử vừa gọi vốn thành công vòng Series A. Hôm nay hệ thống phục vụ 5.000 người dùng một cách mượt mà. Nhưng ban giám đốc vừa thông báo: chiến dịch marketing dịp 12/12 dự kiến kéo về 200.000 lượt truy cập đồng thời trong ba ngày. Câu hỏi đặt lên bàn của bạn không phải là "code có sạch không", mà là: hệ thống của chúng ta có gánh được không, và nếu không thì phải làm gì?
Đó chính là bài toán scalability — khả năng mở rộng. Và trong tất cả các quyết định kiến trúc mà một kỹ sư senior phải đưa ra, việc chọn scale up (theo chiều dọc) hay scale out (theo chiều ngang) là một trong những quyết định nền tảng nhất. Nó ảnh hưởng trực tiếp đến chi phí hạ tầng, độ phức tạp vận hành, khả năng chịu lỗi, và cả tốc độ phát triển sản phẩm của cả đội.
Điều nguy hiểm là nhiều người nhầm lẫn: họ nghĩ "scalability" đồng nghĩa với "microservices" hay "cloud" hay "Kubernetes". Không phải vậy. Scalability là một thuộc tính của hệ thống, và có nhiều con đường để đạt được nó — mỗi con đường có cái giá riêng. Bài học này giúp bạn phân biệt rạch ròi hai hướng mở rộng, hiểu bản chất trade-off của từng hướng, và quan trọng nhất là biết khi nào chọn cái nào. Đây là kỹ năng phân biệt một kỹ sư "biết dựng hệ thống" với một kiến trúc sư "biết ra quyết định".
Khái niệm cốt lõi
Scalability là gì?
Scalability là khả năng của một hệ thống xử lý được lượng tải (load) tăng thêm — nhiều người dùng hơn, nhiều dữ liệu hơn, nhiều giao dịch hơn — mà không bị suy giảm hiệu năng quá mức (degrade), và làm được điều đó với chi phí hợp lý.
Có ba từ khóa quan trọng trong định nghĩa này:
- Tải (load): không chỉ là số lượng người dùng. Tải có thể là số request mỗi giây (RPS), khối lượng dữ liệu lưu trữ, số kết nối đồng thời, hay độ phức tạp của mỗi truy vấn. Một hệ thống có thể scale tốt về số user nhưng lại "chết" khi dữ liệu vượt ngưỡng.
- Không suy giảm quá mức: không hệ thống nào giữ nguyên hiệu năng tuyệt đối khi tải tăng. Vấn đề là mức suy giảm có nằm trong giới hạn chấp nhận được hay không. Nếu tải tăng gấp đôi mà độ trễ chỉ tăng 10%, đó là scale tốt. Nếu tải tăng 20% mà hệ thống sập, đó là hệ thống không scale được.
- Chi phí hợp lý: đây là phần người ta hay quên. Bạn luôn có thể mua server đắt hơn. Câu hỏi kiến trúc là: chi phí tăng có tuyến tính với tải không, hay tăng theo cấp số nhân?
Hai hướng mở rộng cơ bản
Về bản chất, chỉ có hai cách để cho hệ thống nhiều "sức mạnh" hơn.
Vertical scaling (Scale up) — mở rộng theo chiều dọc. Bạn lấy chính cỗ máy đang chạy và làm nó to hơn, mạnh hơn: thêm CPU, thêm RAM, đổi ổ SSD nhanh hơn, nâng băng thông mạng. Ví dụ, trên AWS bạn chuyển từ instance t3.medium (2 vCPU, 4GB RAM) lên m5.2xlarge (8 vCPU, 32GB RAM), rồi lên m5.24xlarge (96 vCPU, 384GB RAM). Vẫn là một máy, chỉ là máy đó ngày càng khủng.
Horizontal scaling (Scale out) — mở rộng theo chiều ngang. Thay vì làm một máy to hơn, bạn thêm nhiều máy giống nhau và chia tải ra cho chúng. Từ 1 server lên 3 server, lên 10 server, lên 100 server, đặt sau một load balancer để phân phối request. Mỗi máy có thể khiêm tốn, nhưng tổng sức mạnh của cả cụm (cluster) thì rất lớn.
Một cách ví von tôi hay dùng khi giảng: scale up giống như thay một đầu bếp giỏi bằng một siêu đầu bếp làm việc nhanh gấp mười; scale out giống như thuê thêm mười đầu bếp bình thường cùng nấu song song. Siêu đầu bếp thì đơn giản để quản lý nhưng có giới hạn sinh học — con người chỉ nhanh đến một mức nào đó. Mười đầu bếp thì cần một người điều phối, cần chia việc rõ ràng, nhưng về lý thuyết bạn có thể thuê thêm gần như vô hạn.
So sánh trực diện
Về giới hạn: Vertical scaling có trần cứng. Máy chủ mạnh nhất thế giới vẫn có giới hạn về số nhân CPU và dung lượng RAM. Khi bạn đã dùng instance to nhất mà nhà cung cấp cloud có, bạn hết đường lên. Horizontal scaling về lý thuyết không có trần — cần thêm thì thêm máy.
Về độ phức tạp: Vertical scaling cực kỳ đơn giản. Ứng dụng của bạn không cần biết nó đang chạy trên máy to hay nhỏ — code y hệt. Horizontal scaling đòi hỏi ứng dụng phải được thiết kế để chạy phân tán: phải stateless (không lưu trạng thái cục bộ trên từng máy), phải xử lý được chuyện session nằm ở đâu, cache đồng bộ ra sao, dữ liệu chia thế nào.
Về khả năng chịu lỗi (fault tolerance): Đây là điểm mấu chốt mà nhiều người bỏ qua. Vertical scaling tạo ra single point of failure — một máy duy nhất, máy đó chết là cả hệ thống chết. Horizontal scaling có tính redundancy sẵn: một máy trong mười máy chết, chín máy còn lại vẫn phục vụ, người dùng có thể không nhận ra.
Về chi phí: Vertical scaling có đường cong chi phí phi tuyến. Nâng từ máy 4GB lên 8GB RAM có thể chỉ đắt gấp đôi. Nhưng nâng từ 192GB lên 384GB thường đắt hơn gấp ba, bốn lần — vì phần cứng cao cấp luôn có "thuế cao cấp". Horizontal scaling có chi phí gần tuyến tính: mười máy thường tốn xấp xỉ mười lần một máy.
Về downtime khi mở rộng: Muốn scale up một máy vật lý hay một database instance thường phải khởi động lại — nghĩa là có downtime. Scale out có thể thêm máy trong lúc hệ thống vẫn chạy, không gián đoạn.
Định luật cần nhớ: không phải cứ thêm máy là nhanh gấp bội
Có một hiểu lầm phổ biến: "thêm gấp đôi số máy thì xử lý được gấp đôi tải". Thực tế không đơn giản vậy, vì mọi hệ thống đều có phần không thể song song hóa — phần phải chạy tuần tự, ví dụ như ghi vào một database trung tâm, hay lấy một khóa (lock) dùng chung.
Đây chính là nội dung của Định luật Amdahl (Amdahl's Law): nếu chỉ 90% công việc có thể song song hóa, thì dù bạn có thêm bao nhiêu máy đi nữa, tốc độ tối đa cũng chỉ nhanh lên được khoảng 10 lần — vì 10% tuần tự kia luôn là nút cổ chai. Bài học rút ra: horizontal scaling chỉ hiệu quả khi bạn thiết kế để giảm phần dùng chung xuống mức thấp nhất. Nếu mọi request vẫn phải đi qua một database duy nhất, thì thêm bao nhiêu web server cũng vô ích — bạn chỉ đang dời nút cổ chai chứ không xóa nó.
Tình huống thực tế
Ví dụ 1: Startup fintech Việt Nam chọn scale up để đi nhanh
Một startup ví điện tử ở TP.HCM (gọi là "VíNhanh") trong giai đoạn đầu có khoảng 30.000 người dùng, xử lý chừng 150 giao dịch mỗi phút vào giờ cao điểm. Toàn bộ backend chạy trên một database PostgreSQL đặt trên một instance AWS RDS db.r5.large (2 vCPU, 16GB RAM).
Khi lượng người dùng tăng lên 120.000 sau một chiến dịch hoàn tiền, database bắt đầu chậm — các truy vấn báo cáo số dư mất 3-4 giây. Đội kỹ thuật chỉ có 4 người, và họ đang chạy đua với thời gian ra mắt tính năng mới. Quyết định của họ: scale up database lên db.r5.2xlarge (8 vCPU, 64GB RAM). Chỉ mất một lệnh nâng cấp và khoảng 5 phút downtime vào lúc 3 giờ sáng. Độ trễ về lại dưới 400ms.
Bài học: Với một đội nhỏ, đang ở giai đoạn cần tốc độ ra sản phẩm, scale up là quyết định đúng đắn về mặt kinh doanh dù nó "kém sang" về mặt kỹ thuật. Họ mua thêm thời gian với chi phí thấp, không phải viết lại kiến trúc, không phải giải quyết bài toán phân tán phức tạp. Với dữ liệu tài chính cần tính nhất quán mạnh (strong consistency), một database tập trung mạnh mẽ còn đơn giản và an toàn hơn nhiều so với việc chia nhỏ dữ liệu quá sớm. Scale up không phải là "giải pháp tạm bợ" — nó là giải pháp đúng cho đúng giai đoạn.
Ví dụ 2: Sàn TMĐT gặp trần scale up trong ngày sale lớn
Trở lại kịch bản mở đầu. Một sàn thương mại điện tử khu vực Đông Nam Á (tương tự mô hình Shopee, Lazada) ban đầu chạy tầng ứng dụng (application layer) trên vài instance lớn. Đến mùa sale 11/11, tải tăng vọt gấp 40 lần so với ngày thường. Họ không thể chỉ mua máy to hơn — vì đơn giản không có máy nào đủ to để gánh 40 lần tải, và kể cả có thì một máy chết là mất trắng doanh thu trong giờ vàng.
Giải pháp của họ là horizontal scaling cho tầng ứng dụng: thiết kế các web/API server thành stateless (session lưu ở Redis dùng chung thay vì trong bộ nhớ từng máy), đặt sau load balancer, và bật auto-scaling — tự động tăng từ 20 instance ngày thường lên hơn 300 instance trong giờ cao điểm, rồi tự thu về khi hết sale để tiết kiệm chi phí.
Điểm tinh tế: họ scale out tầng ứng dụng (dễ, vì stateless), nhưng tầng database thì dùng chiến lược khác — kết hợp read replicas (nhiều bản sao chỉ đọc để chia tải đọc) và caching mạnh. Vì việc scale out một database ghi (write) là bài toán khó hơn nhiều, thuộc phạm vi các kỹ thuật như sharding.
Bài học: Ở quy mô lớn với tải biến động cực mạnh theo mùa, horizontal scaling gần như là bắt buộc — không chỉ vì hiệu năng mà vì khả năng chịu lỗi và khả năng co giãn theo nhu cầu. Và bạn không cần scale mọi tầng theo cùng một cách: tầng dễ (stateless app) scale ngang, tầng khó (database ghi) thì tối ưu bằng replica và cache trước.
Ví dụ 3: Cái bẫy "scale out sớm" của một team SaaS
Một công ty SaaS B2B (phần mềm quản lý nhân sự cho doanh nghiệp vừa) nghe theo trào lưu, ngay từ đầu đã dựng kiến trúc phân tán phức tạp: nhiều service nhỏ, mỗi service scale ngang riêng, chạy trên cụm Kubernetes. Họ có... 800 người dùng.
Kết quả: đội 6 kỹ sư dành 60% thời gian để vận hành hạ tầng — debug lỗi mạng giữa các service, xử lý sự cố đồng bộ, chỉnh cấu hình cluster — thay vì làm tính năng khách hàng cần. Chi phí hạ tầng cho 800 user cao gần bằng một công ty có 50.000 user chạy kiến trúc đơn giản. Tốc độ phát triển sản phẩm chậm lại thấy rõ.
Một cố vấn kỹ thuật vào rà soát và khuyến nghị: gộp lại, chạy trên vài instance lớn (scale up), đơn giản hóa vận hành. Sau ba tháng, đội lấy lại được năng suất và chi phí giảm 70%.
Bài học: Horizontal scaling có giá của nó — cái giá là độ phức tạp vận hành. Scale out khi bạn chưa cần là một dạng "tối ưu hóa sớm", và nó âm thầm rút cạn nguồn lực đội ngũ. Nguyên tắc vàng: scale up cho đến khi bạn có lý do cụ thể, đo được, buộc phải scale out. Đừng mua sự phức tạp bằng tiền của tương lai khi hiện tại chưa cần.
Hướng dẫn từng bước
Khi đứng trước một quyết định mở rộng hệ thống, hãy đi theo trình tự sau:
- Đo trước, đoán sau. Đừng scale dựa trên cảm giác. Hãy có số liệu: RPS hiện tại và dự kiến, độ trễ P50/P99, mức sử dụng CPU/RAM, kích thước dữ liệu và tốc độ tăng. Không có số liệu thì mọi quyết định scale đều là đánh bạc.
- Xác định nút cổ chai thực sự (bottleneck). Hệ thống chậm ở đâu? Tầng ứng dụng hết CPU? Database quá tải I/O? Băng thông mạng? Scale nhầm chỗ là đốt tiền vô ích — thêm web server trong khi bottleneck nằm ở database sẽ chẳng giải quyết gì.
- Ưu tiên tối ưu trước khi mở rộng. Trước khi mua thêm phần cứng, hãy hỏi: có index nào thiếu không? Có truy vấn N+1 nào không? Có thể thêm cache không? Đôi khi một index đúng chỗ giúp giảm tải hơn cả việc nâng gấp đôi server — và miễn phí.
- Cân nhắc scale up trước cho các tầng đơn giản. Nếu chỉ cần thêm chút sức mạnh và đội bạn nhỏ, scale up thường là lựa chọn nhanh, rẻ, ít rủi ro nhất. Dùng nó như bước đệm.
- Chuẩn bị cho scale out bằng cách làm ứng dụng stateless. Ngay cả khi chưa scale ngang, hãy thiết kế app để không lưu trạng thái cục bộ (session, file tạm, cache trong bộ nhớ máy). Đây là khoản đầu tư rẻ giúp bạn scale out dễ dàng khi thời điểm đến, mà không phải viết lại.
- Khi buộc phải scale out, thêm load balancer và cơ chế phân phối. Đặt các instance sau một load balancer, cấu hình health check để tự loại máy hỏng, và cân nhắc auto-scaling nếu tải biến động theo thời gian.
- Ghi lại quyết định. Vì sao bạn chọn scale up hay scale out, ở ngưỡng nào thì nên đổi hướng — hãy ghi lại để cả đội và "bạn của tương lai" hiểu bối cảnh.
Lỗi thường gặp & mẹo
Lỗi 1 — Scale out quá sớm. Như ví dụ 3, dựng kiến trúc phân tán khi mới vài trăm user là tự bắn vào chân. Mẹo: đặt ngưỡng cụ thể trước ("khi CPU database vượt 70% liên tục trong giờ cao điểm, hoặc khi một instance to nhất vẫn không đủ") rồi mới chuyển hướng.
Lỗi 2 — Quên rằng scale up có trần và có single point of failure. Máy càng to càng đắt phi tuyến, và một máy vẫn là một điểm chết. Mẹo: kể cả khi scale up, hãy luôn có ít nhất một máy dự phòng (standby) để failover, đừng bao giờ để đúng một máy gánh toàn bộ hệ thống production quan trọng.
Lỗi 3 — Scale nhầm tầng. Thêm web server trong khi bottleneck ở database. Mẹo: luôn xác định bottleneck bằng số liệu (monitoring) trước khi scale.
Lỗi 4 — Tưởng thêm gấp đôi máy là nhanh gấp đôi. Do phần tuần tự dùng chung (Định luật Amdahl), lợi ích giảm dần khi thêm máy. Mẹo: giảm tối đa tài nguyên dùng chung — dùng cache, giảm khóa (lock), tách trạng thái ra khỏi đường xử lý chính.
Lỗi 5 — Nhầm "scalable" với "microservices/Kubernetes". Một monolith được thiết kế stateless vẫn có thể scale ngang tuyệt vời sau một load balancer. Công cụ không tạo ra scalability — thiết kế mới tạo ra nó.
Mẹo tổng quát: Hãy ghi nhớ nguyên tắc "scale up rồi mới scale out". Bắt đầu bằng cỗ máy to nhất hợp lý về chi phí, và chỉ scale out khi bạn chạm giới hạn thực sự của scale up hoặc cần khả năng chịu lỗi mà một máy không thể mang lại.
Bài tập thực hành
Bài 1 — Chẩn đoán hướng scale. Một blog tin tức Việt Nam chạy trên một VPS, ngày thường 2.000 lượt xem/ngày, mọi thứ mượt. Nhưng khi một bài viết lên top tìm kiếm, lượng truy cập vọt lên 50.000 lượt trong vài giờ rồi trở về bình thường. Bạn nên scale up hay scale out? Vì sao? (Gợi ý: hãy nghĩ về tính biến động của tải và vai trò của caching/CDN cho nội dung tĩnh.)
Bài 2 — Tìm trần. Bạn đang chạy một database trên instance 32GB RAM, và tải dự kiến sẽ tăng gấp 8 lần trong năm tới. Hãy liệt kê ba câu hỏi bạn cần trả lời để quyết định xem scale up còn "đủ đường" không, hay đã đến lúc phải tính đến các kỹ thuật scale out cho database.
Bài 3 — Chuẩn bị stateless. Cho một ứng dụng web đang lưu session người dùng và file upload tạm trong bộ nhớ và ổ đĩa của chính máy chủ. Hãy chỉ ra chính xác những gì cản trở nó scale ngang, và đề xuất cách khắc phục cho từng điểm để nó trở thành stateless.
Bài 4 — Ước lượng chi phí. Giả sử scale up từ 32GB lên 64GB RAM đắt gấp 2 lần, từ 64GB lên 128GB đắt gấp 2,5 lần, và từ 128GB lên 256GB đắt gấp 3 lần. Trong khi đó ba máy 32GB (scale out) chỉ đắt gấp 3 lần một máy 32GB. Ở ngưỡng nào thì scale out trở nên rẻ hơn scale up? Điều này nói lên gì về đường cong chi phí của hai hướng?
Tóm tắt
- Scalability là khả năng hệ thống gánh thêm tải mà không suy giảm quá mức và với chi phí hợp lý — nó là một thuộc tính thiết kế, không phải một công nghệ cụ thể.
- Vertical scaling (scale up): làm một máy mạnh hơn. Ưu điểm: đơn giản, không cần đổi code, ít rủi ro. Nhược điểm: có trần cứng, chi phí tăng phi tuyến, và tạo single point of failure.
- Horizontal scaling (scale out): thêm nhiều máy và chia tải. Ưu điểm: gần như không giới hạn, chi phí tuyến tính, có sẵn khả năng chịu lỗi. Nhược điểm: đòi hỏi ứng dụng stateless và độ phức tạp vận hành cao hơn nhiều.
- Định luật Amdahl nhắc ta rằng thêm máy không giúp nhanh tỷ lệ thuận vô hạn — phần tài nguyên dùng chung là nút cổ chai cần giảm tối đa.
- Nguyên tắc thực chiến: đo trước, tìm đúng bottleneck, tối ưu trước khi mở rộng, scale up cho đến khi có lý do cụ thể buộc phải scale out, và luôn thiết kế stateless để giữ cửa scale out luôn rộng mở.