CAP Theorem: quyết định theo từng tính năng, không theo hệ thống
Xem thửTình huống: một sự cố mạng, bốn tính năng
Lúc 14:20, đường truyền giữa hai vùng hạ tầng của gdev-shop bị gián đoạn trong 6 phút. Cả hai phía đều còn sống — chỉ là không nói chuyện được với nhau.
Trong 6 phút đó, hệ thống phải quyết định cho từng tính năng: tiếp tục phục vụ, hay từ chối?
Tính năng Hậu quả nếu trả lời SAI Hậu quả nếu KHÔNG trả lời
-----------------------------------------------------------------------------------------
1. Xác nhận thanh toán Trừ tiền hai lần Khách không mua được
2. Đếm lượt thích bài đánh giá Số hiển thị lệch vài đơn vị Nút bấm không phản hồi
3. Trừ tồn kho khi đặt hàng Bán vượt số hàng có thật Khách không đặt được
4. Hiển thị danh sách sản phẩm Giá hiển thị cũ vài phút Trang trắng, không xem được
Dừng lại. Với mỗi tính năng, chọn CP (từ chối phục vụ để giữ đúng) hay AP (tiếp tục phục vụ, chấp nhận có thể sai), và viết một câu lý do.
Cách đọc hình kiến trúc. Đường truyền giữa hai vùng đứt sáu phút nhưng cả hai phía còn sống, và quyết định phải đưa ra cho từng tính năng dựa trên hậu quả nếu trả lời sai so với nếu không trả lời. Xác nhận thanh toán chọn CP vì trừ tiền hai lần là thiệt hại trực tiếp. Trừ tồn kho chọn CP hoặc AP kèm xin lỗi và hoàn tiền, đó là quyết định nghiệp vụ. Bộ đếm lượt thích và danh sách sản phẩm chọn AP vì lệch vài đơn vị hay giá cũ vài phút là phiền toái nhỏ, còn trang trắng mất doanh thu ngay.
Đang tải sơ đồ…
Đọc sơ đồ: cùng một sự cố mạng, hai tính năng, hai quyết định
Đây là biểu đồ trình tự có ba cột: client, node A ở phía thiểu số và node B ở phía đa số. Hai khung màu là hai quyết định khác nhau được đưa ra trong cùng một sự cố.
- Ghi chú đầu: the link between A and B is down, both nodes are alive. Đường truyền giữa hai vùng đứt nhưng cả hai node đều khỏe. Node A không liên lạc được với đa số nên không thể lập quorum, tức không thể xác nhận rằng mình đang thấy dữ liệu mới nhất.
- Khung đỏ, lựa chọn CP: refuse rather than risk being wrong. Client yêu cầu xác nhận thanh toán đơn 8821 tới node A. Node A không thể chắc chắn rằng đơn này chưa được xử lý ở phía bên kia, nên trả lỗi hoặc để timeout (mũi tên đứt có dấu x). Khách không thanh toán được lúc này, nhưng tiền không bị trừ hai lần. Thà từ chối và để khách thử lại.
- Khung xanh, lựa chọn AP: serve from local state, reconcile later. Client tăng bộ đếm lượt thích ở node B. Node B trả
200 OKdựa trên dữ liệu có sẵn tại chỗ. Con số có thể lệch vài đơn vị so với phía bên kia, nhưng không ai nhận ra và nó tự đúng lại sau khi hòa giải.
Điều cần rút ra. Đây là cùng một hệ thống và cùng một sự cố nhưng hai tính năng chọn hai hướng ngược nhau, và cả hai đều đúng. Vì thế CAP không phải thuộc tính của cả hệ thống. Nó là một quyết định cho từng tính năng. Câu hỏi dùng để quyết định là: trong lúc partition, trả lời sai hay không trả lời, cái nào gây thiệt hại lớn hơn?
Lưu ý khi đọc. Sơ đồ đặt tính năng thanh toán ở phía thiểu số chỉ để minh họa hành vi từ chối. Bảng đáp án bên dưới trình bày đủ bốn tính năng của gdev-shop cùng lý do chọn cho từng cái.
Đang tải Animation…
Đáp án và reasoning
| Tính năng | Lựa chọn | Vì sao |
|---|---|---|
| Xác nhận thanh toán | CP | Trừ tiền hai lần là thiệt hại tài chính trực tiếp và phải hoàn tiền thủ công. Thà từ chối và để khách thử lại |
| Đếm lượt thích | AP | Số lệch vài đơn vị không ai nhận ra và tự đúng lại sau khi hòa giải. Nút không phản hồi thì ai cũng thấy |
| Trừ tồn kho | CP (hoặc AP kèm bù trừ) | Bán vượt tồn kho gây huỷ đơn và mất uy tín. Một số sàn chọn AP kèm cơ chế xin lỗi và hoàn tiền — đó là quyết định nghiệp vụ, không phải kỹ thuật |
| Hiển thị sản phẩm | AP | Giá cũ vài phút là phiền toái nhỏ; trang trắng là mất doanh thu ngay lập tức |
Điểm quan trọng nhất không nằm ở từng dòng, mà ở hình dạng của bảng: cùng một hệ thống, cùng một sự cố mạng, hai tính năng chọn CP và hai tính năng chọn AP — và cả bốn lựa chọn đều đúng.
CAP không phải một thuộc tính của hệ thống. Nó là một quyết định cho từng tính năng, và câu hỏi luôn là: trong lúc partition, "trả lời sai" hay "không trả lời" cái nào gây thiệt hại lớn hơn?
Nếu bạn thấy mình đang trả lời câu hỏi "hệ thống của chúng ta là CP hay AP?", bạn đang đặt sai câu hỏi ngay từ đầu.
Cách đọc hình kiến trúc. Hình trên là CAP: khi có partition, vốn hiếm, phải chọn giữa phục vụ tiếp (availability, có thể cũ hoặc phân kỳ) và từ chối thao tác rủi ro (consistency). Hình dưới là phần bổ sung của PACELC: khi mạng khỏe, tức phần lớn thời gian, vẫn phải chọn giữa latency thấp (trả lời từ replica gần nhất) và consistency (phối hợp ở mỗi thao tác và trả giá bằng độ trễ). Vì vậy câu hỏi consistency liên quan tới cả những ngày không có sự cố.
Đang tải Animation…
Đang tải sơ đồ…
Đọc sơ đồ: câu hỏi quyết định CP hay AP cho một tính năng
Sơ đồ là một cây quyết định ngắn có một câu hỏi, hai nhánh và hai danh sách ví dụ. Câu hỏi luôn bắt đầu bằng cụm "for this feature", nghĩa là bạn trả lời cho từng tính năng chứ không cho cả hệ thống.
- For this feature, during a partition, which failure is worse? Chỉ xét lúc mạng bị chia cắt, một tình huống hiếm nhưng quyết định phải được đưa ra từ trước. Có hai loại thất bại để so sánh.
- Returning a wrong or stale answer. Nếu đây là thất bại tệ hơn, chọn CP: từ chối phục vụ để giữ đúng. Ví dụ là xác nhận thanh toán, trừ tồn kho và số dư tài khoản, nơi trả lời sai nghĩa là trừ tiền hai lần hay bán vượt số hàng có thật.
- Returning no answer at all. Nếu đây là thất bại tệ hơn, chọn AP: phục vụ từ dữ liệu có sẵn rồi hòa giải sau. Ví dụ là bộ đếm lượt thích, lượt xem, dòng thời gian và cache phiên, nơi số lệch vài đơn vị là phiền toái nhỏ còn nút không phản hồi thì ai cũng thấy.
Hai điều sơ đồ không nói. Thứ nhất, chọn AP không kết thúc khi mạng hồi phục: bạn nợ một kế hoạch hòa giải, và phần chữ ở cuối bài so sánh ba chiến lược. Thứ hai, CAP chỉ mô tả lúc có partition. Khi mạng bình thường bạn vẫn phải chọn giữa độ trễ và tính nhất quán, đó là điều mô hình PACELC mở rộng thêm.
Cách dùng. Với mỗi tính năng trong thiết kế, điền hai cột hậu quả giống bảng ở đầu bài: hậu quả nếu trả lời sai và hậu quả nếu không trả lời. Cột nào nặng hơn quyết định nhánh.
Hiểu CAP cho đúng
Phát biểu quen thuộc "chọn 2 trong 3" là cách diễn đạt gây hiểu nhầm nhiều nhất trong toàn bộ System Design. Chính Eric Brewer — người đưa ra CAP — đã viết lại điều này trong bài nhìn lại sau 12 năm, nhấn mạnh rằng cách hiểu "chọn 2 trong 3" là quá đơn giản và dễ dẫn tới kết luận sai.
Ba điểm cần nắm:
1. CAP chỉ mô tả hành vi khi có partition. Lúc mạng bình thường, bạn hoàn toàn có thể có cả consistency mạnh lẫn availability cao. CAP không bắt bạn phải hy sinh gì cả trong điều kiện bình thường — nó chỉ nói rằng khi partition xảy ra, bạn buộc phải chọn một trong hai.
2. "Chọn CA" không phải một lựa chọn hợp lệ. Partition không phải tính năng bạn bật/tắt — nó là sự kiện xảy ra với bạn. Bất kỳ hệ thống nào chạy trên nhiều máy qua mạng đều sẽ gặp partition. Nói "chúng tôi chọn CA" chỉ có nghĩa là "chúng tôi chưa quyết định sẽ làm gì khi partition xảy ra" — và khi đó hệ thống sẽ tự chọn giúp bạn, thường theo cách tệ nhất.
3. Lựa chọn không phải nhị phân, mà là một phổ. Giữa "từ chối mọi thứ" và "chấp nhận mọi thứ" có rất nhiều mức trung gian: chỉ từ chối các thao tác ghi quan trọng, phục vụ dữ liệu cũ kèm cảnh báo cho người dùng, hoặc cho phép ghi rồi đưa vào hàng chờ hòa giải. Bài tiếp theo của chương sẽ đi sâu vào phổ này.
Và một mở rộng rất hữu ích trong thực tế — thường được gọi là PACELC: nếu có Partition (P) thì chọn giữa Availability và Consistency; còn Else (E), khi mạng bình thường, vẫn phải chọn giữa Latency và Consistency. Điểm bổ sung này quan trọng vì phần lớn thời gian hệ thống của bạn không hề có partition — nhưng bạn vẫn đang trả giá bằng latency cho mỗi mức consistency cao hơn.
Nguồn
- Eric Brewer — "CAP Twelve Years Later: How the 'Rules' Have Changed", IEEE Computer (2012).
Cách đọc hình kiến trúc. Node A có đồng hồ chạy nhanh nên ghi lúc thực tế 12:00:05 nhưng đóng dấu 12:00:09. Node B có đồng hồ chậm nên ghi lúc thực tế 12:00:06 (muộn hơn) nhưng đóng dấu 12:00:04. Khi nối lại, last-write-wins giữ timestamp lớn hơn và loại bỏ cập nhật của B dù nó mới hơn trong thực tế, không để lại dấu vết trong log lỗi. Chỉ chấp nhận được khi mất một cập nhật không gây hậu quả, còn với giỏ hàng hay số dư đó là mất dữ liệu im lặng.
Cách đọc hình kiến trúc. Bộ đếm là 100 trước khi mạng đứt. Phía A cộng thêm 30 (cục bộ thành 130), phía B cộng thêm 20 (cục bộ thành 120). Last-write-wins chỉ giữ 130 hoặc 120 nên mất 20 hoặc 30 lượt thích. Hợp nhất theo ngữ nghĩa cộng cả hai thay đổi: 100 cộng 30 cộng 20 bằng 150, đúng với thực tế. Với tập hợp thì lấy hợp, còn khi không thể hợp nhất tự động thì giữ cả hai phiên bản cho ứng dụng hoặc người dùng quyết định.
Đang tải Animation…
Chọn AP nghĩa là bạn nợ một kế hoạch hòa giải
Đây là phần hay bị bỏ qua nhất khi vẽ trên whiteboard: chọn AP không kết thúc khi mạng hồi phục. Lúc đó mới bắt đầu phần khó.
Trong 6 phút partition, hai phía đã nhận những lệnh ghi khác nhau cho cùng một dữ liệu. Khi nối lại, ai đúng?
Có ba chiến lược, và chúng không tương đương nhau:
| Chiến lược | Cách hoạt động | Khi nào an toàn |
|---|---|---|
| Last-write-wins | Giữ bản ghi có timestamp mới hơn | Chỉ an toàn khi mất một cập nhật là chấp nhận được. Với giỏ hàng hay số dư, đây là mất dữ liệu im lặng |
| Hợp nhất theo ngữ nghĩa | Bộ đếm thì cộng dồn, tập hợp thì hợp nhất | An toàn và nên ưu tiên khi kiểu dữ liệu cho phép — ví dụ lượt thích, lượt xem |
| Giữ cả hai phiên bản | Lưu cả hai và để ứng dụng hoặc người dùng quyết định | An toàn nhất về dữ liệu, nhưng đẩy độ phức tạp sang tầng ứng dụng hoặc sang trải nghiệm người dùng |
Lưu ý cái bẫy của last-write-wins: nó là mặc định của rất nhiều hệ thống vì đơn giản, nhưng "mới hơn" dựa trên đồng hồ của các máy khác nhau — vốn không bao giờ đồng bộ hoàn hảo. Hệ quả là một cập nhật hợp lệ có thể bị ghi đè mà không ai biết, và nó không để lại dấu vết nào trong log lỗi.
Khi chọn AP, câu hỏi bắt buộc phải trả lời ngay tại thời điểm thiết kế là: "Dữ liệu này hòa giải bằng cách nào?" — chứ không phải để tới lúc sự cố mới nghĩ.
Hiểu lầm cần phá bỏ
Hiểu lầm 1 — "Hệ thống của chúng tôi là AP" (hoặc CP)
Câu này gần như luôn sai vì nó áp một lựa chọn cho toàn hệ thống. Thanh toán và bộ đếm lượt thích trong cùng một sản phẩm cần hai lựa chọn ngược nhau.
Hiểu lầm 2 — "Chúng tôi chọn CA vì hạ tầng mạng rất ổn định"
Partition không phải lựa chọn. Mạng ổn định chỉ nghĩa là partition hiếm, không phải không xảy ra — và sự cố GitHub ở chương trước cho thấy 43 giây là quá đủ để gây hậu quả kéo dài nhiều ngày.
Hiểu lầm 3 — "CAP nghĩa là phải hy sinh consistency để có hiệu năng"
CAP không nói gì về hiệu năng lúc bình thường. Phần "đánh đổi giữa consistency và latency khi mạng khỏe" là nội dung của PACELC, không phải CAP.
Hiểu lầm 4 — "Chọn AP xong là xử lý được sự cố"
Chọn AP nghĩa là bạn nợ một cơ chế hòa giải. Không có nó, dữ liệu phân kỳ sẽ tồn tại vĩnh viễn và âm thầm — loại lỗi tệ nhất vì không có cảnh báo nào.
Hiểu lầm 5 — "Last-write-wins là mặc định an toàn"
Nó dựa trên đồng hồ của các máy khác nhau và âm thầm huỷ một cập nhật hợp lệ. Nó chỉ chấp nhận được khi mất một bản cập nhật thực sự không gây hậu quả.
Góc nhìn phỏng vấn System Design
CAP là chủ đề rất dễ nói sáo rỗng, nên interviewer thường kiểm tra bằng cách đẩy về tình huống cụ thể.
Interviewer có thể hỏi
- "Hệ thống của bạn là CP hay AP?" — đây là bẫy. Câu trả lời tốt là hỏi lại: tính năng nào?
- "Trong lúc partition, nút thêm vào giỏ hàng nên hoạt động hay báo lỗi?"
- "Bạn chọn AP cho bộ đếm. Khi mạng hồi phục, hai phía có số khác nhau thì xử lý sao?"
- "Vì sao không chọn CA?"
- "Mạng của chúng tôi rất ổn định, CAP có còn quan trọng không?" — cơ hội để nhắc tới PACELC.
Câu trả lời yếu và câu trả lời tốt hơn
Yếu: "Hệ thống em thiết kế theo hướng AP để đảm bảo tính sẵn sàng cao."
Tốt hơn: "Em quyết định theo từng tính năng, dựa trên việc trong lúc partition thì 'trả lời sai' hay 'không trả lời' gây thiệt hại lớn hơn. Xác nhận thanh toán và trừ tồn kho em chọn CP vì trả sai nghĩa là trừ tiền hai lần hoặc bán vượt hàng — thà từ chối và để khách thử lại. Bộ đếm lượt thích và hiển thị danh sách sản phẩm em chọn AP vì dữ liệu lệch vài phút không ai bị ảnh hưởng thật, còn trang trắng thì mất doanh thu ngay. Với phần AP, em thiết kế sẵn cách hòa giải: bộ đếm dùng phép cộng dồn thay vì last-write-wins, vì last-write-wins dựa trên đồng hồ các máy và sẽ âm thầm huỷ một cập nhật hợp lệ."
Tự kiểm tra — không nhìn lại bài
- CAP mô tả hành vi của hệ thống trong điều kiện nào? Nó có nói gì về lúc mạng bình thường không?
- Vì sao "chúng tôi chọn CA" là một lỗi phân loại chứ không phải một lựa chọn kiến trúc?
- Nêu hai tính năng trong cùng một sản phẩm thương mại điện tử cần lựa chọn CP/AP ngược nhau, kèm lý do.
- Chọn AP tạo ra nghĩa vụ gì mà nhiều thiết kế bỏ quên?
- Vì sao last-write-wins nguy hiểm, và khi nào nó chấp nhận được?
- PACELC bổ sung điều gì so với CAP, và vì sao phần bổ sung đó liên quan tới phần lớn thời gian vận hành?
Chuyển sang bài toán khác
gdev-feed trong lúc partition có bốn tính năng: đăng bài mới, đọc feed, đếm lượt thích, và gửi tin nhắn riêng.
- Tính năng nào chọn CP, tính năng nào chọn AP? Giải thích bằng hậu quả nghiệp vụ.
- Tin nhắn riêng có một đặc điểm khiến nó khó hơn bộ đếm: thứ tự tin nhắn quan trọng. Điều này ảnh hưởng thế nào tới cách hòa giải sau partition?
- Nếu hai phía cùng cho phép đăng bài trong lúc partition, người dùng có thể thấy gì bất thường khi mạng hồi phục?
Đáp án chi tiết cho các câu hỏi trong bài
Đáp án cho phần Tự kiểm tra
- CAP chỉ mô tả hành vi khi có partition. Với mạng bình thường, nó không bắt hy sinh gì: có thể vừa có consistency mạnh vừa có availability cao. Phần đánh đổi giữa consistency và latency lúc mạng khỏe thuộc về PACELC chứ không phải CAP.
- Partition không phải tính năng bật tắt mà là sự kiện xảy ra với bạn: mọi hệ thống chạy trên nhiều máy qua mạng đều sẽ gặp partition. Nói "chọn CA" thực chất là chưa quyết định sẽ làm gì khi partition xảy ra, nên hệ thống sẽ tự chọn giúp, thường theo cách tệ nhất. Vì vậy đó là lỗi phân loại.
- Ví dụ: xác nhận thanh toán chọn CP (trừ tiền hai lần là thiệt hại tài chính, thà từ chối và để khách thử lại) và bộ đếm lượt thích chọn AP (lệch vài đơn vị không ai nhận ra và tự đúng lại, còn nút không phản hồi thì ai cũng thấy). Cặp khác: trừ tồn kho (CP) và hiển thị danh sách sản phẩm (AP).
- Nghĩa vụ hòa giải: khi mạng hồi phục, hai phía đã nhận những lệnh ghi khác nhau cho cùng dữ liệu, nên phải có cơ chế quyết định ai đúng. Câu hỏi "dữ liệu này hòa giải bằng cách nào?" phải trả lời ngay lúc thiết kế, nếu không dữ liệu phân kỳ sẽ tồn tại vĩnh viễn và âm thầm.
- Last-write-wins dựa trên timestamp từ đồng hồ của các máy khác nhau, vốn không bao giờ đồng bộ hoàn hảo, nên một cập nhật hợp lệ có thể bị ghi đè mà không ai biết và không có dấu vết trong log. Chấp nhận được khi mất một cập nhật thực sự không gây hậu quả (ví dụ thời điểm xem gần nhất, dữ liệu cache).
- PACELC bổ sung: else (E), khi mạng bình thường, vẫn phải chọn giữa latency và consistency. Nó liên quan tới phần lớn thời gian vận hành vì hệ thống hầu như không bị partition, nhưng mỗi mức consistency cao hơn đều tốn latency ở mỗi thao tác.
Đáp án cho bài toán chuyển sang gdev-feed
- Đăng bài mới: AP. Từ chối đăng bài làm người dùng mất nội dung vừa viết. Mỗi bài là bản ghi mới có id riêng nên hai phía ít xung đột, hòa giải dễ (gộp hai tập bài). Đọc feed: AP, feed cũ vài phút không gây hại, trang trắng thì có. Đếm lượt thích: AP với hợp nhất cộng dồn. Tin nhắn riêng: nên chấp nhận gửi (AP) vì từ chối hay mất tin nhắn gây hậu quả lớn hơn, nhưng phải giữ thứ tự; phương án khác là gắn mỗi cuộc hội thoại vào một vùng nhà và chọn CP cho riêng việc gửi.
- Thứ tự tin nhắn ảnh hưởng đến hòa giải: tuyệt đối không dùng last-write-wins vì nó sẽ bỏ tin nhắn. Phải giữ cả hai tập tin nhắn (hợp nhất bằng phép hợp) rồi sắp xếp theo quan hệ nhân quả: mỗi tin mang id tin trước đó hoặc số thứ tự theo từng người gửi trong hội thoại, kèm quy tắc phá hòa xác định (ví dụ thời gian logic rồi id người gửi) để mọi phía hiển thị cùng một thứ tự.
- Bất thường người dùng có thể thấy khi hai phía cùng cho đăng bài: bài xuất hiện muộn với thời gian cũ chen vào giữa feed, câu trả lời hiện trước bài gốc, số đếm nhảy giá trị, bài đăng trùng nếu client gửi lại ở cả hai phía (cần khóa idempotency), và bài đã xóa ở một phía có thể hiện lại.
Đáp án gợi ý cho các câu hỏi phỏng vấn
- "Hệ thống của bạn là CP hay AP?" Hỏi lại: tính năng nào? Xác nhận thanh toán và trừ tồn kho chọn CP, bộ đếm và danh sách sản phẩm chọn AP, dựa trên việc trong lúc partition trả lời sai hay không trả lời gây thiệt hại lớn hơn.
- "Nút thêm vào giỏ lúc partition?" Nên hoạt động (AP) vì thất bại là mất doanh thu ngay, đổi lại phải có hòa giải: giỏ hàng hợp nhất bằng phép hợp các món, chấp nhận một món đã xóa có thể hiện lại.
- "Bộ đếm AP, mạng hồi phục hai phía khác nhau?" Dùng hợp nhất theo ngữ nghĩa: cộng các phần tăng của từng phía thay vì chọn một giá trị, ví dụ 100 cộng 30 cộng 20 bằng 150. Last-write-wins sẽ làm mất lượt thích.
- "Vì sao không chọn CA?" Vì partition là sự kiện chứ không phải lựa chọn. CA nghĩa là chưa quyết định hành vi khi partition và hệ thống sẽ tự chọn theo cách tệ nhất.
- "Mạng rất ổn định, CAP còn quan trọng không?" Vẫn quan trọng vì partition hiếm nhưng không phải không có (43 giây đủ gây hậu quả hàng ngày), và PACELC cho thấy ngay cả khi mạng khỏe vẫn phải đánh đổi latency với consistency ở mỗi thao tác.
Nếu chỉ nhớ 3 điều
- CAP chỉ nói về lúc có partition, và lựa chọn là theo từng tính năng, không theo hệ thống.
- "Chọn CA" không tồn tại — partition là sự kiện xảy ra với bạn, không phải tuỳ chọn.
- Chọn AP nghĩa là nợ một cơ chế hòa giải, và last-write-wins là cách âm thầm làm mất dữ liệu.