
Định lý CAP là nguyên lý nền tảng trong thiết kế hệ thống phân tán, phát biểu rằng một cơ sở dữ liệu phân tán không thể đồng thời bảo đảm cả tính nhất quán, tính sẵn sàng và khả năng chịu lỗi phân vùng mạng. Bài viết này giải thích CAP là gì, ý nghĩa thực tiễn của từng thành phần và vì sao định lý này thường được hiểu sai trong phỏng vấn kỹ thuật.

Định lý CAP là gì?
Định lý CAP — còn gọi là định lý Brewer — được nhà khoa học máy tính Eric Brewer đề xuất và sau đó được chứng minh chặt chẽ bởi Sagi Gilbert và Nancy Lynch. Cả hai bài đăng tại Wikipedia: CAP theorem.
Định lý phát biểu rằng khi mạng bị chia cắt thành hai phần không liên lạc được, hệ thống phân tán buộc phải lựa chọn: hoặc giữ tính nhất quán bằng cách từ chối phục vụ, hoặc giữ tính sẵn sàng bằng cách trả về dữ liệu có thể đã cũ. Không có cách nào thoát khỏi ngưỡng lựa chọn này.
Điểm quan trọng nhất cần nhấn mạnh: ngưỡng lựa chọn chỉ phát sinh khi có phân vùng mạng thực sự. Trong điều kiện bình thường, hệ thống vẫn có thể đạt cả ba thuộc tính cùng lúc. Vì vậy câu nói “hệ thống phân tán chỉ chọn được hai trong ba” là cách giải thích đơn giản hóa, dù dễ nhớ.

Ba thành phần của CAP
Consistency (tính nhất quán). Mọi lần đọc đều nhận được giá trị mới nhất từ một lần ghi nào đó, hoặc báo lỗi. Nói cách khác, mọi node đều thấy cùng một trạng thái dữ liệu tại cùng một thời điểm. Yêu cầu này rất đắt: một lần ghi chỉ được coi là thành công khi nó đã được nhân bản tới toàn bộ các node còn lại.
Availability (tính sẵn sàng). Mỗi yêu cầu gửi tới một node đang hoạt động phải nhận được phản hồi — khung hồi chẩn đoán trong thời gian hữu hạn. Lưu ý phản hồi này không bắt buộc phải chứa bản dữ liệu mới nhất. Đây là định nghĩa của Gilbert và Lynch, khác với khái niệm high availability trong kiến trúc phần mềm.
Partition tolerance (khả năng chịu lỗi phân vùng). Hệ thống tiếp tục hoạt động bình thường dù mạng có tùy ý số lượng tin nhắn bị mất hoặc bị trễ giữa các node.
| Yếu tố | Ý nghĩa thực tế | Cách đo |
|---|---|---|
| Consistency | Tất cả node cùng thấy một trạng thái | Đọc sau ghi phải thấy giá trị mới nhất |
| Availability | Luôn có phản hồi cho mọi yêu cầu | Tỉ lệ yêu cầu nhận được câu trả lời |
| Partition tolerance | Không sập khi mạng đứt đoạn | Thời gian hoạt động liên tục qua sự cố mạng |
Phân vùng mạng không phải lỗi hiếm gặp
Đây là điểm mà nhiều lập trình viên hiểu sai. Mạng phân tán luôn có phân vùng: một switch hỏng, một sợi cáp bị đào, một vùng cloud gặp sự cố, hay đơn giản là một packet bị mất trên đường đi. Trong hệ phân tán quy mô lớn, phân vùng là bình thường, không phải ngoại lệ.
Vì vậy câu hỏi thực tế mà định lý CAP buộc bạn phải trả lời không phải “nên chọn hai trong ba”, mà là:
- CP — chấp nhận không sẵn sàng để giữ nhất quán. Ví dụ: MongoDB với thiết lập đa số phiếu, HBase, ZooKeeper. Phù hợp khi dữ liệu sai còn tệ hơn không có dữ liệu, như hệ thống đồng thuận, khóa phân tán, kho chứng từ.
- AP — chấp nhận dữ liệu có thể cũ để luôn sẵn sàng. Ví dụ: Cassandra, Riak, DynamoDB với eventual consistency. Phù hợp cho mạng xã hội, cập nhật số liệu, nơi người dùng chấp nhận nhìn thấy dữ liệu chậm vài giây.
- CA — bảo đảm nhất quán và sẵn sàng, nhưng chỉ khi không có phân vùng. Đây là lựa chọn của một cơ sở dữ liệu đơn máy như PostgreSQL hay MySQL chạy trên một node. Không mở rộng được ra nhiều node mà vẫn giữ đảm bảo này.
Định lý PACELC — mở rộng đầy đủ hơn
Định lý CAP chỉ mô tả tình huống khi hệ thống đang gặp sự cố. Phần còn lại của thời gian — khi mọi thứ hoạt động bình thường — cũng có một sự đánh đổi: latency hay consistency. Đây chính là nội dung của chữ E trong PACELC:
- Nếu có Partition — phải chọn giữa Availability hay Consistency.
- Else (nếu không có phân vùng) — vẫn phải chọn giữa P (latency, tức độ nhất quán) hay Consistency.
Nói cách khác, ngay cả khi mọi node đều hoạt động, bạn vẫn phải trả giá. Quyết định đọc trong vùng đồng ý (consistency zone) sẽ chậm hơn nhưng chính xác; quyết định đọc ở gần người dùng sẽ nhanh hơn nhưng có thể dữ liệu cũ. Đây chính là lý do các hệ thống như Cassandra có cấu hình read consistency tùy chỉnh theo từng loại truy vấn.

Áp dụng định lý CAP vào thiết kế hệ thống
Ví dụ một hệ thống cho phép người dùng đặt vé sự kiện. Nếu có hai người cùng mua được chỗ cuối cùng ở hai node khác nhau, hệ thống đã vi phạm tính nhất quán nghiêm trọng. Vì vậy ở bước giữ chỗ, hệ thống buộc phải ưu tiên C — dù phải chậm hơn và có thể báo lỗi cho người dùng khi hệ thống quá tải.
Nhưng ở màn hình danh sách sự kiện, việc người dùng không thấy sự kiện mới vừa được thêm trong 2 giây là không đáng kể. Ở đây ưu tiên A — luôn trả về kết quả, chấp nhận dữ liệu hơi cũ.
Cùng một hệ thống, hai thành phần khác nhau chọn hướng khác nhau. Đó là cách định lý CAP thực sự trở thành công cụ thiết kế chứ không phải gánh nhãn lý thuyết.
Những hiểu lầm phổ biến
“CAP bắt ta phải bỏ một trong ba luôn.” Sai. Sự đánh đổi chỉ xảy ra khi có phân vùng mạng. Ngoài điều kiện đó, hệ thống có thể đạt cả ba. Đây là lý do nhiều tài liệu kiến trúc hiện đại khuyến nghị dùng khung PACELC thay vì CAP đơn thuần.
“Consistency trong CAP giống consistency của ACID.” Không đúng. Consistency của CAP là mô hình linearizable — mọi lần đọc thấy trạng thái mới nhất, tức mạnh hơn nhiều so với tính nhất quán cấp độnh của ACID.
“Chọn CP nghĩa là hệ thống sẽ sập.” Chọn CP nghĩa là hệ thống từ chối phục vụ một phần yêu cầu trong lúc phân vùng để không phục vụ dữ liệu sai. Nó vẫn hoạt động, chỉ là chọn an toàn hơn là chọn sẵn sàng.
CAP và các công nghệ phổ biến
| Cơ sở dữ liệu | Hướng CAP | Ghi chú |
|---|---|---|
| PostgreSQL đơn node | CA | Không có phân vùng khi chạy một node |
| etcd / ZooKeeper | CP | Ưu tiên nhất quán tuyệt đối cho đồng thuận |
| MongoDB đa node | CP | Mặc định dùng Write Concern đa số phiếu |
| Cassandra / Riak | AP | Cho phép cấu hình consistency tùy truy vấn |
| DynamoDB | AP | Eventual consistency mặc định |
Kết luận
Định lý CAP không phải lựa chọn bị áp đặt từ trên xuống mà là công cụ để đặt câu hỏi đúng: trong hệ thống của bạn, lúc nào việc mất tính nhất quán là không chấp nhận được, và lúc nào độ trễ thấp quan trọng hơn? Hệ thống tốt là hệ thống trả lời được câu hỏi này rõ ràng, thay vì mặc định chọn theo thói quen.
Điều đáng nhớ nhất: trong một hệ phân tán thực sự, phân vùng mạng luôn xảy ra. Vì vậy câu hỏi không phải “nên bỏ thuộc tính nào”, mà là khi mạng đứt, hệ thống của bạn sẽ hành xử theo hướng nào — và người dùng có chấp nhận được hành vi đó không.
