Tổng quan System Design
Xem thửMột buổi sáng đẹp trời của gdev-shop
Bạn vừa viết xong gdev-shop, một web bán hàng nhỏ: một server chạy ứng dụng, một database PostgreSQL, tất cả đặt trên cùng một máy. Với 50 người dùng thử nghiệm, mọi thứ chạy mượt. Rồi marketing thông báo đợt khuyến mãi lúc 12 giờ trưa và hàng nghìn người cùng bấm "Đặt hàng". Trang chậm dần, một số người nhận lỗi, vài đơn bị trừ tiền nhưng không thấy trạng thái. Server không "viết sai code", nó chỉ chưa bao giờ được thiết kế cho tình huống này.
Đây chính là lý do System Design tồn tại.
System Design là gì?
System Design là quá trình biến yêu cầu sản phẩm thành một kiến trúc có thể xây dựng và vận hành: chọn các thành phần (server, database, cache, queue...), quyết định chúng nói chuyện với nhau như thế nào, dữ liệu được lưu và đọc ở đâu, và hệ thống hành xử ra sao khi tải tăng hoặc khi một phần bị hỏng.
Một bản thiết kế tốt trả lời được năm câu hỏi:
| Câu hỏi | Ví dụ với gdev-shop |
|---|---|
| Hệ thống phải làm gì? | Xem sản phẩm, thêm giỏ hàng, đặt hàng, thanh toán, theo dõi đơn |
| Hệ thống phải làm tốt đến mức nào? | Trang sản phẩm tải dưới 300 ms, hệ thống hoạt động 99,9% thời gian |
| Tải lớn cỡ nào? | Giả định 50.000 người dùng mỗi ngày, đợt khuyến mãi gấp 10 lần ngày thường |
| Dữ liệu sống ở đâu? | Đơn hàng trong PostgreSQL, ảnh sản phẩm trong object storage, giỏ hàng tạm trong Redis |
| Hỏng thì sao? | Database chính chết, cổng thanh toán chậm, một server ứng dụng bị treo |
Vì sao phải thiết kế trước khi code?
Có hai loại quyết định trong phần mềm. Quyết định dễ đổi (tên hàm, màu nút, một đoạn logic) sửa trong vài phút. Quyết định khó đổi (chia dữ liệu thế nào, dùng một hay nhiều database, các dịch vụ gọi nhau đồng bộ hay bất đồng bộ) khi hệ thống đã có dữ liệu thật và người dùng thật thì sửa tốn hàng tuần và có rủi ro mất dữ liệu.
System Design tập trung vào loại thứ hai: những quyết định mà đổi sau rất đắt. Mục tiêu không phải dự đoán chính xác tương lai, mà là không tự khóa mình vào ngõ cụt và biết trước điểm nào sẽ nghẽn đầu tiên.
Bản đồ một web application điển hình
Trước khi học từng thành phần, hãy nhìn bức tranh tổng thể mà cả khóa học sẽ lần lượt đi sâu. Đây là cách một request từ người dùng đi qua một web application phổ biến:
Cách đọc hình
Đọc theo các số trên hình, đi theo một lượt tải trang của gdev-shop:
- Client (trình duyệt hoặc app) biết tên miền
shop.gdev.devnhưng chưa biết địa chỉ IP. Nó hỏi DNS, hệ thống phân giải tên miền thành địa chỉ IP. - Ảnh sản phẩm, CSS, JavaScript là các file ít thay đổi. Chúng có thể được phục vụ từ CDN, mạng máy chủ đặt gần người dùng, để giảm tải cho server ứng dụng và giảm độ trễ.
- Các lời gọi API (xem giỏ hàng, đặt hàng) đi vào Load Balancer, thành phần phân phối request đến một trong nhiều App Server.
- App Server chạy logic nghiệp vụ. Với dữ liệu hay được đọc (thông tin sản phẩm), nó kiểm tra Cache trước. Cache không có thì nó mới truy vấn Database, nơi lưu dữ liệu bền vững.
- Việc không cần chờ kết quả ngay (gửi email xác nhận, xuất hóa đơn PDF) được đẩy vào Message Queue để Worker xử lý nền. Người dùng không phải chờ.
- Response đi ngược lại: App Server, Load Balancer, Client.
Nét liền là đường người dùng phải chờ, nét đứt là việc chạy nền sau khi đã trả lời. Thành phần cốt lõi của gần như mọi web application là Client, Application Server và Database; Load Balancer trở thành cốt lõi ngay khi bạn chạy nhiều hơn một server. Các thành phần còn lại là tùy chọn: bạn chỉ thêm khi có số đo hoặc lý do rõ ràng cho thấy cần.
Đang tải Animation…
Mỗi thành phần giải quyết một vấn đề cụ thể và cũng mang theo một chi phí cụ thể. Cache làm đọc nhanh hơn nhưng đưa vào nguy cơ dữ liệu cũ. Queue giúp phản hồi nhanh nhưng việc xử lý chỉ hoàn tất sau đó. Nhiều server giúp chịu tải nhưng buộc ứng dụng phải không giữ trạng thái trong bộ nhớ. Các lesson sau sẽ đi vào từng cặp "vấn đề, giải pháp, cái giá".
Nếu một thành phần gặp sự cố thì điều gì xảy ra là câu hỏi quan trọng không kém câu hỏi nó làm gì. Chỉ có một App Server, nó chết là cả dịch vụ chết. Chỉ có một Database, nó chết là mất toàn bộ khả năng đặt hàng. Cache chết thì hệ thống vẫn chạy được nhưng chậm hơn và Database phải gánh nhiều hơn. Mỗi mũi tên trong hình đều là một điểm có thể hỏng.
Quy trình thiết kế
Người thiết kế giỏi không bắt đầu bằng việc chọn công nghệ. Họ đi theo một chuỗi bước, và khóa học này bám theo chuỗi đó:
Đang tải sơ đồ…
Đang tải Animation…
Đang tải Animation…
Cách đọc quy trình
- Requirements: yêu cầu chức năng (hệ thống làm gì) và phi chức năng (làm tốt đến mức nào: độ trễ, tính sẵn sàng, tính nhất quán).
- Capacity estimate: ước lượng số request mỗi giây, dung lượng lưu trữ, băng thông. Đây là cơ sở để biết một server có đủ hay không.
- API and data model: hợp đồng giữa client và server, cùng cách tổ chức dữ liệu theo cách nó sẽ được truy cập.
- High-level architecture: các hộp và mũi tên, request đi đâu, dữ liệu ghi và đọc ở đâu.
- Trade-offs and bottlenecks: chọn A thì mất gì ở B, điểm nào sẽ nghẽn trước.
- Failure and scale plan: nếu thành phần X chết thì sao, nếu tải tăng 10 lần thì sửa chỗ nào trước.
Đây là vòng lặp chứ không phải đường thẳng. Phát hiện bottleneck ở bước 5 thường khiến bạn quay lại sửa kiến trúc ở bước 4.
Ví dụ ước lượng nhanh (số liệu giả định)
Giả định gdev-shop có 50.000 người dùng hoạt động mỗi ngày, mỗi người tạo khoảng 20 request. Tổng cộng 1.000.000 request mỗi ngày. Một ngày có 86.400 giây, vậy trung bình khoảng 12 request mỗi giây. Giờ cao điểm thường gấp vài lần trung bình, đợt khuyến mãi có thể gấp 10 lần: khoảng 120 request mỗi giây. Một server ứng dụng được tối ưu hợp lý thường xử lý được con số cỡ này. Kết luận quan trọng là chưa cần kiến trúc phức tạp cho mức tải này, và đó là một kết luận hợp lệ của System Design. Các con số ở đây chỉ để minh họa cách suy luận, không phải số đo thực tế của một hệ thống cụ thể.
Must Know, Good to Know, Advanced
- Must Know: phân biệt yêu cầu chức năng và phi chức năng; biết các thành phần cốt lõi trong bản đồ phía trên và vai trò của từng thành phần; luôn hỏi "tải bao nhiêu" và "hỏng thì sao" trước khi chọn công nghệ.
- Good to Know: ước lượng nhanh (back-of-the-envelope) và phân biệt giá trị trung bình với giá trị cao điểm.
- Advanced: thiết kế đa vùng (multi-region), tối ưu chi phí hạ tầng ở quy mô lớn. Khóa cơ bản chỉ giới thiệu.
Những hiểu lầm phổ biến
- "System Design là vẽ càng nhiều hộp càng tốt." Sai. Mỗi hộp thêm vào là một thứ phải vận hành, giám sát và có thể hỏng. Thiết kế tốt là chọn ít thành phần nhất mà vẫn đáp ứng yêu cầu.
- "Chỉ hệ thống lớn như mạng xã hội mới cần System Design." Sai. Hệ thống nhỏ vẫn cần quyết định về dữ liệu và độ tin cậy. Khác biệt là câu trả lời thường đơn giản hơn: một server và một database có thể là thiết kế đúng.
- "Chọn công nghệ trước, lý do tính sau." Dùng Kafka vì "ai cũng dùng" là dấu hiệu thiết kế theo mốt. Công nghệ là câu trả lời, requirement mới là câu hỏi.
- "Có kiến trúc rồi thì không bao giờ phải đổi." Kiến trúc tiến hóa cùng yêu cầu. Thiết kế tốt là thiết kế biết rõ giới hạn hiện tại và dấu hiệu cho thấy đã đến lúc nâng cấp.
Tự kiểm tra
- Trong bản đồ phía trên, thành phần nào là cốt lõi, thành phần nào tùy chọn? Điều gì khiến bạn thêm một thành phần tùy chọn vào?
gdev-shopcó 120 request mỗi giây vào giờ cao điểm. Bạn có cần Load Balancer và Cache ngay không? Lập luận dựa trên điều gì?- Vì sao quyết định "dùng một hay nhiều database" được xem là quyết định khó đổi?
Bài thực hành
Chọn một ý tưởng ứng dụng nhỏ (đặt lịch cắt tóc, quản lý lớp học, bán vé sự kiện). Viết đúng năm dòng: ba yêu cầu chức năng và hai yêu cầu phi chức năng có con số cụ thể (ví dụ độ trễ dưới bao nhiêu mili giây, bao nhiêu người dùng đồng thời). Đây là đầu vào của mọi bài System Design về sau.
Đáp án gợi ý
- Cốt lõi của gần như mọi web app là Client, App Server và Database; Load Balancer trở thành cốt lõi ngay khi bạn chạy nhiều hơn một server. Tùy chọn là CDN, Cache, Queue với Worker, Object storage. Chỉ thêm một thành phần tùy chọn khi có bằng chứng nó giải quyết một vấn đề đã đo được, ví dụ cache khi database bị đọc lặp lại quá nhiều, queue khi có việc chậm không cần người dùng chờ.
- Chưa chắc. 120 request mỗi giây là con số nhỏ, một vài instance ứng dụng cộng một database được chỉ mục tốt thường chịu được. Lập luận dựa trên tải đo được, độ trễ mục tiêu và tỉ lệ đọc ghi: nếu P99 vẫn đạt và database chưa bão hòa thì Cache chưa cần. Load Balancer có thể đáng thêm sớm vì cần chạy từ hai instance trở lên để chịu lỗi, nhưng đó là lý do về độ sẵn sàng chứ không phải về tải.
- Vì dữ liệu có trọng lực: sau khi hàng triệu dòng đã nằm ở một nơi, và nhiều dịch vụ đã phụ thuộc vào lược đồ đó, việc tách hoặc gộp phải di chuyển dữ liệu, đổi truy vấn, xử lý giao dịch xuyên database và thường cần chạy song song hai hệ thống trong lúc chuyển. Code ứng dụng sửa được bằng một lần triển khai, còn dữ liệu thì không.
Nếu chỉ nhớ 3 điều
- System Design là quá trình quyết định những thứ khó đổi về sau, xuất phát từ requirement và con số tải, không phải từ công nghệ.
- Một web application điển hình đi theo đường Client, DNS, CDN, Load Balancer, App Server, Cache, Database, cộng thêm Queue và Worker cho việc nền. Nhiều thành phần là tùy chọn.
- Mỗi thành phần giải quyết một vấn đề nhưng đem theo một cái giá. Thiết kế tốt không phải là thiết kế nhiều thành phần nhất.