Mở đầu — vì sao bài này quan trọng
Trước khi bạn học cách viết một ADR, cân nhắc trade-off giữa Kafka và RabbitMQ, hay quyết định chọn SQL hay NoSQL, bạn cần một nền móng tư duy chung: System Design là gì và nó giải quyết bài toán gì. Bài học này là viên gạch đầu tiên của cả khóa. Nếu bạn bỏ qua nó, mọi bài sau sẽ giống như những mảnh ghép rời rạc — bạn biết từng công cụ nhưng không biết đặt chúng vào đâu.
Với vai trò một người ra quyết định kiến trúc (dù bạn là Tech Lead, Senior Engineer đang lên, hay một Founder kỹ thuật), System Design không phải là chuyện "vẽ box và mũi tên cho đẹp". Nó là kỹ năng biến một yêu cầu kinh doanh mơ hồ — "app phải chịu được lượng người dùng tăng gấp 10 lần vào dịp Tết" — thành một tập hợp các quyết định kỹ thuật có căn cứ, có thể bảo vệ được trước hội đồng review, và quan trọng nhất là không làm sập hệ thống lúc 2 giờ sáng.
Điều khiến System Design khó không phải là thiếu kiến thức công nghệ. Ngày nay bạn có thể Google ra hàng trăm bài về load balancer trong 5 giây. Cái khó là tư duy đánh đổi: mọi quyết định kiến trúc đều lấy đi thứ gì đó để đổi lấy thứ khác. Bài này sẽ giúp bạn hình dung được bức tranh tổng thể — các chiều mà mọi hệ thống đều phải cân nhắc — trước khi đi sâu vào từng chủ đề chuyên biệt ở các bài sau. Ở đây chúng ta tập trung riêng vào một trụ cột nền tảng: khả năng mở rộng (scalability) và cách tư duy về nó, vì đây là điểm khởi đầu tự nhiên nhất cho mọi cuộc trò chuyện về thiết kế hệ thống.
Khái niệm cốt lõi
System Design là gì, ở mức nền tảng
System Design là quá trình định nghĩa kiến trúc, thành phần, giao diện và luồng dữ liệu của một hệ thống để thỏa mãn một tập yêu cầu. Yêu cầu ở đây có hai loại: yêu cầu chức năng (functional — hệ thống làm gì, ví dụ "cho phép người dùng đặt hàng") và yêu cầu phi chức năng (non-functional — hệ thống chạy tốt đến đâu, ví dụ "phải trả kết quả dưới 200ms cho 99% request"). Cả khóa học này về bản chất là học cách ra quyết định để cân bằng hai loại yêu cầu đó.
Ở bài đầu tiên, hãy nắm ba nguyên tắc nền:
- Không có kiến trúc "đúng", chỉ có kiến trúc phù hợp với bối cảnh. Một startup 3 người và một ngân hàng có 20 triệu khách hàng cần hai lời giải hoàn toàn khác nhau cho cùng một bài toán "lưu trữ giao dịch". Bối cảnh quyết định lời giải.
- Mọi thứ đều là đánh đổi. Bạn muốn hệ thống nhanh hơn? Thường phải trả bằng chi phí hạ tầng hoặc độ phức tạp. Bạn muốn dễ mở rộng? Có thể phải hy sinh tính đơn giản trong vận hành.
- Thiết kế cho hiện tại, chừa đường cho tương lai. Over-engineering (thiết kế thừa cho quy mô bạn chưa có) cũng nguy hiểm không kém under-engineering.
Scalability — trụ cột của bài này
Scalability (khả năng mở rộng) là khả năng của hệ thống xử lý được lượng tải (load) tăng lên mà vẫn duy trì hiệu năng chấp nhận được. "Tải" có thể là số người dùng đồng thời, số request mỗi giây (RPS/QPS), lượng dữ liệu phải lưu, hay số lượng giao dịch.
Điểm cốt yếu cần khắc sâu: scalable không có nghĩa là nhanh. Một hệ thống có thể rất nhanh khi có 100 người dùng nhưng không scalable — tức là khi lên 100.000 người dùng thì nó sập. Ngược lại, một hệ thống scalable là hệ thống mà khi tải tăng, bạn có một con đường rõ ràng để tăng năng lực xử lý tương ứng, thường bằng cách thêm tài nguyên.
Có hai chiến lược mở rộng nền tảng mà bạn phải phân biệt được rạch ròi (các bài 8 sẽ đi sâu, ở đây ta nắm bản chất):
#### Vertical Scaling (Scale Up) — "máy to hơn"
Vertical scaling là tăng năng lực của chính cỗ máy đang chạy: thêm CPU, thêm RAM, đổi sang ổ SSD nhanh hơn, dùng instance mạnh hơn. Hình dung như việc bạn nâng cấp chiếc xe máy 110cc lên xe 150cc để chở được nặng hơn và chạy nhanh hơn.
- Ưu điểm: Đơn giản. Không cần thay đổi code hay kiến trúc. Không phải lo về đồng bộ dữ liệu giữa nhiều máy. Đây thường là bước đầu tiên hợp lý.
- Nhược điểm: Có trần vật lý — không có cỗ máy nào lớn vô hạn. Chi phí tăng theo cấp số nhân ở đầu cao (một instance 64 vCPU đắt hơn nhiều so với hai instance 32 vCPU cộng lại). Và nó tạo ra single point of failure — chỉ có một máy, máy chết là hệ thống chết.
Horizontal scaling là thêm nhiều cỗ máy chạy song song và phân phối tải giữa chúng, thường thông qua một load balancer (bộ cân bằng tải) đứng phía trước để chia request. Thay vì một xe tải khổng lồ, bạn dùng một đội 10 xe bán tải.
- Ưu điểm: Về lý thuyết mở rộng gần như không giới hạn — cần thêm năng lực thì thêm máy. Tăng độ sẵn sàng (availability): một máy chết, các máy còn lại vẫn phục vụ. Chi phí biên tuyến tính và dễ dự đoán hơn.
- Nhược điểm: Phức tạp hơn nhiều. Ứng dụng phải được thiết kế stateless (không lưu trạng thái cục bộ trên từng máy) hoặc phải quản lý trạng thái chia sẻ. Xuất hiện các bài toán mới: đồng bộ dữ liệu, phân phối tải, xử lý session người dùng — những chủ đề mà nửa sau khóa học sẽ đào sâu.
#### Auto-scaling — mở rộng tự động theo tải
Auto-scaling là cơ chế tự động thêm hoặc bớt máy (thường là horizontal) dựa trên các chỉ số thời gian thực như mức sử dụng CPU, số request đang chờ, hay độ trễ. Nó trả lời cho một vấn đề kinh tế rất thực tế: tải không bao giờ đều. Một sàn thương mại điện tử có thể yên ắng lúc 3 giờ sáng nhưng bùng nổ lúc 12 giờ trưa và cực đại vào các đợt sale.
Nếu bạn cấp phát hạ tầng đủ cho lúc cao điểm, bạn sẽ đốt tiền vào những giờ thấp điểm khi máy chạy không tải. Nếu bạn cấp phát cho mức trung bình, hệ thống sẽ sập lúc cao điểm. Auto-scaling giải bài toán này: đặt một chính sách (ví dụ: "khi CPU trung bình vượt 70% trong 3 phút, thêm 2 instance; khi xuống dưới 30%, bớt đi") và để hệ thống tự điều chỉnh.
Ba yếu tố cần hiểu về auto-scaling:
- Metric kích hoạt (trigger): thường là CPU, memory, hoặc metric ứng dụng như số request trong hàng đợi. Chọn sai metric là lỗi phổ biến — CPU thấp không có nghĩa hệ thống khỏe nếu nút thắt nằm ở database.
- Độ trễ khởi động (warm-up time): thêm một máy mới không tức thời. Nó cần thời gian boot, khởi tạo ứng dụng, "làm nóng" cache. Nếu tải tăng vọt trong 10 giây mà máy mới cần 90 giây để sẵn sàng, auto-scaling vẫn không cứu được đỉnh đó.
- Giới hạn (min/max): luôn đặt trần trên để một cơn tăng bất thường (hay một cuộc tấn công) không khiến hóa đơn cloud của bạn nổ tung, và đặt sàn dưới để hệ thống không bao giờ tụt xuống dưới mức phục vụ tối thiểu.
Tình huống thực tế
Tình huống 1: Sàn TMĐT Việt Nam trong đợt sale 12/12
Hãy tưởng tượng một sàn thương mại điện tử tầm trung tại Việt Nam — gọi là "ChợViệt" — bình thường phục vụ khoảng 2.000 request/giây. Đội kỹ thuật chạy toàn bộ backend trên một server EC2 loại lớn (vertical scaling), và nó hoạt động tốt suốt cả năm.
Rồi đợt sale 12/12 đến. Marketing tung mã giảm 50% lúc 0 giờ. Trong 3 phút đầu, lưu lượng nhảy lên 28.000 request/giây — gấp 14 lần bình thường. Server đơn lẻ đạt 100% CPU, thời gian phản hồi từ 150ms leo lên 12 giây, rồi timeout hàng loạt. Người dùng bấm "mua" nhưng không có phản hồi, nhiều người bấm lại nhiều lần, càng làm tải nặng thêm. Trong 20 phút, ChợViệt mất ước tính 400 triệu đồng doanh thu và vô số khách hàng thất vọng.
Diễn giải: Vấn đề không phải là server yếu — nó thừa sức cho ngày thường. Vấn đề là kiến trúc không scalable: chỉ có một con đường duy nhất để tăng năng lực (mua máy to hơn), và con đường đó không thể kích hoạt trong vài phút. Sau sự cố, đội chuyển sang kiến trúc horizontal: đặt một load balancer phía trước, tách ứng dụng thành stateless để chạy được trên nhiều instance, và bật auto-scaling với chính sách nhân đôi số máy khi RPS vượt ngưỡng. Đợt sale sau, hệ thống tự động scale từ 4 lên 22 instance trong 6 phút và trụ vững.
Bài học: Scalability phải được thiết kế trước, không phải vá lúc khủng hoảng. Và với các business có tính mùa vụ cao (sale, Tết), horizontal + auto-scaling gần như là bắt buộc chứ không phải lựa chọn.
Tình huống 2: Startup fintech scale up quá sớm... nhưng đúng
Một startup fintech Đông Nam Á ở giai đoạn đầu, mới có 5.000 người dùng. Một kỹ sư trong đội, vừa đọc xong loạt bài về microservices và Kubernetes, đề xuất xây ngay hệ thống 12 microservice chạy trên cluster Kubernetes tự quản lý để "sẵn sàng scale tới hàng triệu người dùng".
CTO bác bỏ. Thay vào đó, họ chạy một ứng dụng đơn giản trên hai instance vừa phải với một load balancer, cộng một database được scale up. Toàn bộ hạ tầng mất 3 người-tuần để dựng thay vì 3 người-tháng.
Trong 18 tháng tiếp theo, họ tăng lên 90.000 người dùng, và mỗi khi cần thêm năng lực, họ chỉ đơn giản nâng cấp instance (scale up) hoặc thêm một instance ứng dụng (scale out nhẹ). Họ chưa bao giờ chạm trần buộc phải xé nhỏ hệ thống.
Diễn giải: Đội kỹ sư trẻ nhầm lẫn giữa "scalable" và "phức tạp". Kiến trúc microservices phức tạp có thể scale rất xa, nhưng chi phí vận hành, gỡ lỗi và nhân sự của nó là gánh nặng khổng lồ cho một đội 6 người chưa có product-market fit. Lựa chọn scale up + horizontal nhẹ cho họ một con đường mở rộng đủ xa cho quy mô thực tế, với chi phí công sức nhỏ hơn nhiều.
Bài học: Thiết kế cho quy mô bạn sẽ có trong 12–18 tháng tới, không phải cho quy mô trong mơ. Đây là biểu hiện trực tiếp của nguyên tắc "tránh over-engineering". Scale up thường là câu trả lời đúng lâu hơn bạn nghĩ.
Tình huống 3: Auto-scaling chọn sai metric
Một công ty SaaS xử lý báo cáo tài chính bật auto-scaling dựa trên CPU cho tầng ứng dụng. Vào cuối tháng — thời điểm khách chạy báo cáo lớn — hệ thống chậm khủng khiếp, nhưng auto-scaling không hề thêm máy. Lý do: CPU của các máy ứng dụng chỉ ở mức 40%, vì chúng đang chờ database trả kết quả cho các truy vấn nặng. Nút thắt nằm ở database, không phải ở CPU của tầng ứng dụng.
Thêm bao nhiêu máy ứng dụng cũng vô ích — thậm chí còn tệ hơn, vì mỗi máy mới lại bắn thêm truy vấn vào database vốn đã quá tải, đẩy nó tới bờ vực sập.
Diễn giải: Auto-scaling chỉ tốt bằng metric mà nó dựa vào, và mở rộng một tầng không giải quyết được nút thắt ở tầng khác. Đội phải chuyển metric kích hoạt sang "số request đang chờ trong hàng đợi" (phản ánh đúng độ nghẽn của người dùng), đồng thời xử lý riêng bài toán database.
Bài học: Trước khi tối ưu khả năng mở rộng, hãy xác định nút thắt thật sự. Mở rộng nhầm chỗ vừa tốn tiền vừa không giải quyết vấn đề.
Hướng dẫn từng bước
Đây là quy trình tư duy để tiếp cận bài toán scalability cho một hệ thống bất kỳ. Bạn sẽ dùng khung này rất nhiều trong suốt khóa học.
- Định lượng tải hiện tại và tương lai. Đừng nói "hệ thống phải scale". Hãy nói bằng con số: hiện tại bao nhiêu RPS, đỉnh bao nhiêu, dữ liệu tăng bao nhiêu GB/tháng, dự kiến 12 tháng tới nhân mấy lần. Không có con số thì mọi quyết định chỉ là cảm tính.
- Xác định nút thắt (bottleneck). Với tải dự kiến đó, thành phần nào sẽ "chết" trước? CPU ứng dụng? Băng thông database? Bộ nhớ? I/O đĩa? Dùng số liệu đo đạc thật (benchmark, load test), đừng đoán.
- Chọn chiến lược cho nút thắt đó. Nếu là tầng ứng dụng stateless — thường horizontal + load balancer là câu trả lời. Nếu là database — bài toán khó hơn nhiều (các bài về SQL/NoSQL, caching, CQRS sẽ giúp). Bắt đầu bằng vertical nếu nó đủ và rẻ về công sức.
- Kiểm tra điều kiện tiên quyết cho horizontal. Muốn scale out được, ứng dụng phải stateless: không lưu session hay file cục bộ trên từng máy. Nếu chưa, đây là việc phải làm trước.
- Thiết lập auto-scaling với metric đúng. Chọn metric phản ánh trải nghiệm người dùng (độ trễ, hàng đợi) hơn là chỉ CPU. Đặt min/max rõ ràng. Tính đến warm-up time.
- Load test để kiểm chứng. Đừng tin thiết kế trên giấy. Bắn tải giả lập bằng công cụ (k6, JMeter, Locust) tới mức đỉnh dự kiến rồi vượt lên 1.5–2 lần để tìm điểm gãy thật.
- Ghi lại quyết định. Vì sao chọn scale out thay vì scale up? Đánh đổi gì? Đây chính là hạt giống của một ADR — chủ đề bài kế tiếp trong khóa.
Lỗi thường gặp & mẹo
- Nhầm "nhanh" với "scalable". Hệ thống chạy 20ms với 100 user không đảm bảo gì về hành vi với 100.000 user. Luôn hỏi: "khi tải nhân 10, điều gì gãy trước?"
- Scale nhầm tầng. Thêm máy ứng dụng khi nút thắt là database là sai lầm kinh điển (tình huống 3). Luôn tìm bottleneck thật trước khi mở rộng.
- Quên rằng database khó scale hơn tầng ứng dụng. Tầng ứng dụng stateless scale out dễ như thêm máy. Database có trạng thái — mở rộng nó liên quan đến nhất quán dữ liệu, replica, sharding. Đừng cho rằng "cứ thêm máy" áp dụng được cho database.
- Over-engineering cho quy mô chưa có. Xây kiến trúc phân tán phức tạp cho một sản phẩm mới ra mắt là cách chắc chắn để đội nhỏ kiệt sức mà không tạo giá trị (tình huống 2). Thiết kế cho quy mô của 12–18 tháng tới.
- Auto-scaling không có trần trên. Một vòng lặp lỗi hay một cuộc tấn công có thể khiến auto-scaling nhân máy vô hạn, biến sự cố kỹ thuật thành thảm họa tài chính. Luôn đặt max.
- Bỏ qua warm-up time. Nếu instance mới cần 90 giây để sẵn sàng còn đỉnh tải chỉ kéo dài 30 giây, auto-scaling reactive không cứu được. Với các đợt sale biết trước, hãy pre-scale (scale trước theo lịch).
- Mẹo vàng: Bắt đầu đơn giản, đo đạc thật, mở rộng theo bằng chứng. Kiến trúc tốt nhất là kiến trúc đủ phức tạp để giải bài toán hiện tại và không hơn.
Bài tập thực hành
- Phân loại nhanh: Với mỗi hành động sau, hãy xác định đó là vertical hay horizontal scaling: (a) nâng RAM của database từ 32GB lên 128GB; (b) thêm 5 web server phía sau load balancer; (c) đổi instance ứng dụng từ 4 vCPU sang 16 vCPU; (d) bật auto-scaling để dao động từ 3 đến 15 instance.
- Thiết kế trên giấy: Một app đặt đồ ăn phục vụ 3.000 RPS ngày thường nhưng dự kiến đỉnh 25.000 RPS vào giờ ăn trưa (11h–13h) mỗi ngày. Hãy phác thảo chiến lược scaling: dùng scale up hay scale out? Metric auto-scaling nào? Bạn sẽ pre-scale hay để reactive? Viết ra lý do cho từng lựa chọn.
- Truy tìm bottleneck: Hệ thống của bạn chậm dần khi tải tăng, nhưng CPU tầng ứng dụng chỉ 35%. Liệt kê 4 nguyên nhân gốc có thể (không phải CPU ứng dụng) và cách bạn sẽ kiểm chứng từng cái.
- Tự phản biện: Lấy một hệ thống bạn đang làm (hoặc một app bạn dùng hàng ngày). Ước lượng tải hiện tại, đoán nút thắt sẽ gãy đầu tiên nếu tải nhân 10, và viết 3 câu trả lời cho câu hỏi "hệ thống này có scalable không, và mở rộng bằng cách nào?".
Tóm tắt
System Design, ở mức nền tảng, là kỹ năng biến yêu cầu kinh doanh thành các quyết định kỹ thuật có căn cứ — và trái tim của nó là tư duy đánh đổi. Không có kiến trúc đúng tuyệt đối, chỉ có kiến trúc phù hợp với bối cảnh và quy mô cụ thể.
Trong bài này, chúng ta tập trung vào trụ cột scalability: khả năng hệ thống chịu được tải tăng mà vẫn giữ hiệu năng chấp nhận được. Bạn đã nắm ba công cụ nền:
- Vertical scaling (máy to hơn) — đơn giản, là bước khởi đầu hợp lý, nhưng có trần vật lý và tạo single point of failure.
- Horizontal scaling (nhiều máy hơn qua load balancer) — mở rộng gần như vô hạn và tăng độ sẵn sàng, nhưng đòi hỏi ứng dụng stateless và độ phức tạp cao hơn.
- Auto-scaling — tự động điều chỉnh số máy theo tải, tiết kiệm chi phí, nhưng chỉ tốt bằng metric bạn chọn và phải tính đến warm-up time cùng giới hạn min/max.