[Series System Design - Bài 2] CAP Theorem & PACELC Theorem: Những định lý bất di bất dịch mà mọi kỹ sư backend phải nằm lòng
Chào anh em, chào mừng trở lại với series System Design.
Ở bài trước, chúng ta đã thống nhất với nhau rằng mang code từ Local lên Production giống như việc đem một chiếc thuyền thúng ra giữa đại dương. Sóng to, gió lớn và những sự cố bất ngờ sẽ nhấn chìm hệ thống của bạn nếu thiếu đi Tư duy Thiết kế.
Khi hệ thống bắt đầu phình to, bạn không thể nhét mọi thứ vào một con server duy nhất (Single Node). Bạn buộc phải chia nhỏ nó ra, chạy trên nhiều server khác nhau, kết nối với nhau qua mạng lưới (Distributed System - Hệ thống phân tán).
Và ngay khoảnh khắc bạn chia tách hệ thống, bạn chính thức bước vào lãnh địa của những định lý khắc nghiệt nhất. Chào mừng bạn đến với CAP và PACELC.
Ngày xưa, khi mới vào nghề, tôi từng mạnh miệng tuyên bố trong buổi họp thiết kế hệ thống: "Em sẽ làm một hệ thống mà dữ liệu lúc nào cũng chuẩn xác 100%, không bao giờ chết, kể cả khi đứt cáp quang biển!". Giám đốc kỹ thuật lúc đó chỉ cười nhẹ và bảo tôi về đọc lại định lý CAP.
Đến tận bây giờ, sau hơn 20 năm lăn lộn, tôi nhận ra đó là một định lý "tàn nhẫn" bởi nó ép chúng ta phải thừa nhận: Chúng ta không thể có tất cả.
1. CAP Theorem (Định lý CAP) là gì?
Định lý CAP (do Eric Brewer đưa ra năm 2000) khẳng định rằng: Trong một hệ thống lưu trữ dữ liệu phân tán, bạn chỉ có thể đạt được tối đa 2 trong số 3 đặc tính sau:
- C - Consistency (Tính nhất quán): Mọi client khi đọc dữ liệu tại bất kỳ node nào trong hệ thống, tại cùng một thời điểm, đều phải nhận được kết quả mới nhất. Nếu Node A vừa cập nhật số dư tài khoản, Node B cũng phải trả về đúng số dư đó.
- A - Availability (Tính sẵn sàng): Hệ thống luôn phản hồi mọi request (không báo lỗi, không timeout), kể cả khi một số node trong hệ thống đang bị "chết lâm sàng".
- P - Partition Tolerance (Khả năng chịu đựng chia cắt mạng): Hệ thống vẫn hoạt động bình thường kể cả khi mạng kết nối giữa các node bị đứt gãy (rớt mạng, mất kết nối nội bộ).
Sự thật phũ phàng: Trong thế giới thực, mạng lưới luôn tiềm ẩn rủi ro hỏng hóc (Packet loss, switch hỏng, đứt cáp). Do đó, P là yếu tố bắt buộc (không thể tránh khỏi).
Vì chữ P đã bị "đóng đinh", nên định lý CAP thực chất là một bài toán đánh đổi: Khi xảy ra đứt mạng (Partition), bạn chọn Consistency (CP) hay Availability (AP)?
Kịch bản CP (Consistency > Availability)
Giả sử bạn đang thiết kế hệ thống giao dịch tiền tệ, ví điện tử hoặc hệ thống xử lý tiền mặt tại các máy bán vé tự động (TVM).
- Tình huống: Node A (xử lý trừ tiền) mất kết nối với Node B (cập nhật trạng thái vé).
- Lựa chọn CP: Bạn thà báo lỗi hệ thống, tạm ngưng phục vụ người dùng (hy sinh Availability) còn hơn là để người dùng bị trừ tiền mà không nhả vé, hoặc nhả vé mà chưa trừ tiền (bảo vệ Consistency).
- Hệ cơ sở dữ liệu tiêu biểu: MongoDB, Redis, HBase.
Kịch bản AP (Availability > Consistency) Giả sử bạn thiết kế hệ thống cổng soát vé (Gate) tự động tại nhà ga hoặc hệ thống giỏ hàng của một trang thương mại điện tử.
- Tình huống: Cổng soát vé mất kết nối với máy chủ trung tâm do sự cố mạng.
- Lựa chọn AP: Bạn vẫn cho phép hành khách quẹt thẻ đi qua để tránh kẹt cứng cả nhà ga (ưu tiên Availability). Dữ liệu thẻ sẽ được lưu tạm tại cổng và đồng bộ lại với trung tâm sau khi mạng có lại (Eventual Consistency - Nhất quán sau). Chấp nhận rủi ro thẻ có thể hết tiền nhưng vẫn lọt qua được một lần.
- Hệ cơ sở dữ liệu tiêu biểu: Cassandra, DynamoDB, CouchDB.
2. Nhưng CAP là chưa đủ... Chào mừng đến với PACELC Theorem
Vài năm sau khi CAP ra đời, các kỹ sư nhận ra một điểm mù: Định lý CAP chỉ nói về lúc mạng bị đứt (Partition). Vậy lúc mạng bình thường (không có Partition) thì sao?
Năm 2010, Daniel Abadi đưa ra định lý PACELC, một bản nâng cấp toàn diện của CAP.
PACELC được đọc như một câu lệnh if-else:
- IF 'P' (Nếu xảy ra chia cắt mạng): Chọn 'A' (Sẵn sàng) hoặc 'C' (Nhất quán). (Đây chính là CAP).
- ELSE 'E' (Nếu mạng bình thường): Chọn 'L' (Latency - Độ trễ thấp/Tốc độ) hay 'C' (Consistency - Nhất quán).
Tại sao lúc mạng bình thường lại phải chọn giữa L và C?
Hãy tưởng tượng hệ thống của bạn có 3 node (Master và 2 Slave). Khi có data mới ghi vào Master:
- Nếu bạn muốn Consistency (C): Master phải đợi copy xong data sang cả 2 Slave rồi mới báo "Thành công" cho người dùng. Đổi lại, người dùng phải chờ rất lâu (Hy sinh Latency).
- Nếu bạn muốn Latency (L): Master vừa ghi nhận xong là báo "Thành công" luôn cho người dùng, sau đó mới ngầm đồng bộ sang 2 Slave. Đổi lại, nếu có ai đọc data từ Slave ngay lúc đó, họ có thể thấy dữ liệu cũ (Hy sinh Consistency).
Ví dụ thực tế với PACELC: Hầu hết các hệ thống Cache (như Memcached, Redis cấu hình cluster) thường đi theo mô hình PA/EL. Nghĩa là: Nếu đứt mạng (P), nó vẫn cố gắng trả về dữ liệu cũ (A). Khi mạng bình thường (E), nó ưu tiên phản hồi cực nhanh (L) và chấp nhận việc thỉnh thoảng user đọc được data cũ từ cache trong vài mili-giây (chấp nhận hy sinh C).
Lời kết
Hiểu rõ CAP và PACELC không giúp hệ thống của bạn hết bug, nhưng nó giúp bạn ngừng đi tìm một "chén thánh" hoàn hảo. Là một kỹ sư backend, bạn không thiết kế hệ thống dựa trên sự mơ mộng, bạn thiết kế dựa trên sự lựa chọn và giới hạn của vật lý.
Khi Product Manager yêu cầu bạn làm một tính năng "Vừa phải phản hồi ngay lập tức, vừa phải dữ liệu chuẩn xác 100%, vừa không bao giờ lỗi", hãy mỉm cười, mở bài viết này lên và nhẹ nhàng mời họ đi uống cà phê.
👉 Và ở bài tiếp theo, chúng ta sẽ đào sâu vào chính cái thứ nghệ thuật từ chối và lựa chọn này qua tiêu đề: "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 (Latency vs Throughput, Consistency vs Availability)."
Anh em nhớ đón đọc nhé! Ai từng có pha thiết kế nào "đi vào lòng đất" vì cố chấp chọn cả C lẫn A thì để lại bình luận bên dưới cho xôm tụ nhé!
All rights reserved