0

[Series System Design - Bài 3] Trade-off (Sự đánh đổi): Nghệ thuật tối thượng trong System Design. Không có kiến trúc hoàn hảo, chỉ có kiến trúc phù hợp với bối cảnh

Chào anh em, nếu anh em có thói quen đọc các bài viết về kiến trúc phần mềm, chắc hẳn đã từng thấy những câu hỏi kiểu như: "Nên dùng MySQL hay MongoDB?", "RabbitMQ tốt hơn hay Kafka tốt hơn?", "Kiến trúc này đã hoàn hảo chưa?".

Sau hơn bao năm ăn ngủ với các hệ thống từ startup nhỏ lẻ đến những hệ thống enterprise đồ sộ, tôi có thể trả lời anh em bằng một từ quen thuộc mà mọi Senior/Staff Engineer đều dùng ít nhất 5 lần mỗi ngày: "Tùy" (It depends).

Trong System Design, không có viên đạn bạc (Silver Bullet). Không có công nghệ nào là tuyệt đối tốt, cũng không có kiến trúc nào là hoàn hảo. Bất cứ quyết định nào bạn đưa ra đều có cái giá của nó. Chúng ta gọi đó là Trade-off (Sự đánh đổi).

Hôm nay, chúng ta sẽ cùng mổ xẻ hai cặp phạm trù kinh điển nhất trong nghệ thuật đánh đổi này.

1. Latency (Độ trễ) vs. Throughput (Thông lượng)

Nhiều người thường nhầm lẫn hai khái niệm này với nhau và gọi chung là "Hệ thống chạy nhanh". Nhưng thực tế, thiết kế để tối ưu Latency rất khác với thiết kế để tối ưu Throughput.

  • Latency (Độ trễ): Thời gian cần thiết để xử lý một request (tính bằng ms). Giống như việc một chiếc xe thể thao chạy từ điểm A đến điểm B mất bao lâu.
  • Throughput (Thông lượng): Số lượng request hệ thống có thể xử lý trong một đơn vị thời gian (Requests Per Second - RPS). Giống như việc một con đường có thể cho bao nhiêu chiếc xe đi qua trong một phút.

Bài toán đánh đổi: Hãy tưởng tượng hệ thống cổng soát vé tự động (AFC Gate) tại các nhà ga Metro. Nếu bạn muốn tối ưu Latency để hành khách quẹt thẻ là cửa mở ngay lập tức (dưới 100ms), bạn sẽ phải thiết kế để Gate tự quyết định dựa trên dữ liệu cache offline. Nhưng nếu bạn muốn đảm bảo không có giao dịch gian lận nào lọt qua, bạn bắt mỗi lượt quẹt thẻ đều phải gọi API mã hóa phức tạp về server trung tâm kiểm tra, cập nhật database đầy đủ rồi mới mở cửa. Lúc này Latency có thể lên tới 1-2 giây. Hậu quả? Hàng dài người sẽ kẹt cứng lại ở cổng vào giờ cao điểm, tức là Throughput của toàn bộ nhà ga cắm đầu đi xuống.

Đôi khi, để tăng Throughput tổng thể, bạn phải chấp nhận gom nhóm (batching) các request lại xử lý cùng lúc, điều này vô tình làm tăng Latency của những request đến đầu tiên. Không thể ép một chiếc xe buýt chở được 50 người chạy với tốc độ của một chiếc Ferrari được.

2. Consistency (Nhất quán) vs. Availability (Sẵn sàng)

Như chúng ta đã bàn ở bài về định lý CAP và PACELC, khi hệ thống phân tán gặp sự cố mạng, bạn buộc phải chọn một trong hai. Nhưng ngay cả khi hệ thống bình thường, sự đánh đổi này vẫn diễn ra hàng ngày ở level business logic.

Bài toán đánh đổi: Hãy lấy ví dụ về một hệ thống e-commerce bán lẻ mỹ phẩm trong một đợt Flash Sale cực lớn. Hàng ngàn người đổ xô vào mua một chai serum đang giảm giá.

  • Nếu bạn chọn Consistency (Nhất quán tuyệt đối): Mỗi lần người dùng bấm "Thêm vào giỏ hàng", hệ thống sẽ phải lock (khóa) dòng dữ liệu tồn kho trong database, trừ đi 1, rồi mới nhả lock ra cho người khác làm tiếp. Dữ liệu chuẩn đét, không bao giờ bán lố (oversell). Nhưng cái giá phải trả là database sẽ nghẽn cổ chai, API timeout liên tục, và hệ thống "chết lâm sàng" với hàng ngàn người dùng khác. Bạn mất Availability.
  • Nếu bạn chọn Availability (Tính sẵn sàng cao): Nút "Thêm vào giỏ hàng" chỉ cần ghi nhận qua Message Queue hoặc Cache một cách nhanh chóng. Trải nghiệm người dùng cực kỳ mượt mà, hệ thống vẫn sống khỏe răn re để hứng traffic. Tuy nhiên, rủi ro là bạn có thể bán ra 105 chai serum trong khi kho chỉ có 100 chai (Hy sinh Consistency).

Trong thực tế, người ta thường chọn Availability cho luồng thêm vào giỏ hàng, và dồn sự kiểm tra Consistency chặt chẽ vào bước Thanh toán cuối cùng, hoặc xử lý hoàn tiền (refund) sau đó. Trải nghiệm kinh doanh đôi khi quan trọng hơn sự hoàn hảo của dữ liệu trong ngắn hạn. Tương tự, với các hệ thống máy bán vé tự động (TVM) xử lý tiền mặt, nơi mà "nhét tờ 500k vào thì máy không được phép quên", Consistency lại là ưu tiên sinh tử, thà máy báo lỗi tạm ngưng phục vụ (mất Availability) còn hơn nuốt tiền của khách mà không ghi nhận.

Lời kết: Trách nhiệm của một Kỹ sư Thiết kế

Làm System Design không phải là chọn ra công nghệ xịn nhất, mà là hiểu rõ nỗi đau nào mà business (doanh nghiệp) có thể chịu đựng được.

Bạn ưu tiên trải nghiệm nhanh chóng của người dùng hay tính toàn vẹn của dữ liệu? Bạn ưu tiên chi phí server rẻ hay khả năng chịu tải đột biến? Mọi quyết định đều là sự thỏa hiệp. Một Senior Engineer giỏi là người đưa ra được các option, chỉ rõ ưu khuyết điểm của từng phương án, lưu lại các quyết định đó (thông qua tài liệu Architecture Decision Records - ADR) và tự tin với sự lựa chọn phù hợp nhất cho bối cảnh hiện tại.

Đã nói về những sự lựa chọn và bối cảnh, có một chủ đề luôn khiến các team dev cãi nhau nảy lửa trong các buổi họp. Chia nhỏ hay gộp chung? Đập đi xây lại hay tiếp tục chắp vá?

👉 Ở bài tiếp theo, chúng ta sẽ bước vào chiến trường khốc liệt nhất: "Monolith vs. Microservices: Cuộc chiến không hồi kết. Khi nào thì nên chia nhỏ, khi nào thì nên giữ nguyên?"

Anh em hãy chuẩn bị tinh thần nhé! Trong lúc chờ đợi, anh em thường ưu tiên Latency hay Throughput trong các project hiện tại của mình? Cùng thảo luận bên dưới nào!


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.