Hệ thống Dữ liệu Phân tán/Causal consistency — yếu hơn nhưng đủ, rẻ hơn
34/38
Bài 34 / 38~12 phútNhất quán & Đồng thuậnMiễn phí lượt xem

Causal consistency — yếu hơn nhưng đủ, rẻ hơn

Causal consistency giữ đúng thứ tự nhân quả nhưng không ép thứ tự toàn cục — yếu hơn linearizability, rẻ hơn nhiều, và đủ cho phần lớn ứng dụng.

TL;DR: Causal consistency đảm bảo mọi node thấy các thao tác có quan hệ nhân quả theo đúng thứ tự — nhưng thao tác đồng thời (không ai gây ra ai) được phép thấy khác thứ tự ở mỗi node. Nó yếu hơn linearizability: bỏ ràng buộc thứ tự toàn cục khớp real-time, nên không đảm bảo recency. Bù lại, nó là mức nhất quán mạnh NHẤT vẫn khả dụng khi mạng phân mảnh — chỉ cần theo dõi quan hệ phụ thuộc (vector clock), không cần phối hợp toàn cục, nên rẻ hơn nhiều và đủ cho phần lớn ứng dụng: quan trọng là "câu trả lời không hiện trước câu hỏi", không phải "mọi người thấy đúng thời điểm tuyệt đối".

Bạn bình luận "Ai biết chỉ mình với?" dưới một bài. Bạn của bạn trả lời "Có tôi!". Một người thứ ba mở app — và thấy câu trả lời "Có tôi!" trước câu hỏi "Ai biết chỉ mình với?". Vô nghĩa. Điều bị vỡ ở đây không phải recency (thấy trễ vài giây thì không sao) mà là thứ tự nhân quả: một phản hồi không được xuất hiện trước thứ nó phản hồi. Causal consistency là mô hình vừa đủ để cấm đúng lỗi này mà không phải trả giá đắt của linearizability.

Bài này định nghĩa causal consistency, đối chiếu nó với linearizability (bài 01) theo bốn trục, mổ cơ chế giữ thứ tự nhân quả không cần đồng bộ toàn cục, và chỉ ra vì sao nó là "điểm ngọt" khả dụng-nhất-quán.

1. Analogy — luồng bình luận: trả lời phải sau câu hỏi

Trong một luồng bình luận, có hai loại quan hệ. Nhân quả: bình luận B là trả lời cho A → B phải hiện sau A với mọi người (bạn đọc A mới viết được B). Đồng thời: hai người ở hai nơi cùng bình luận độc lập, không ai đọc của ai → thứ tự giữa hai cái này không quan trọng, người này thấy X-rồi-Y, người kia thấy Y-rồi-X đều chấp nhận được.

Causal consistency đúng là quy tắc đó: ép thứ tự cho cặp có nhân quả, thả tự do cho cặp đồng thời. Nó không đòi cả hệ đồng ý một dòng thời gian duy nhất — chỉ đòi không ai thấy "hậu quả trước nguyên nhân".

Luồng bình luậnCausal consistency
Trả lời phải hiện sau câu hỏiThao tác nhân quả giữ đúng thứ tự ở mọi node
Hai bình luận độc lập, thứ tự tuỳThao tác đồng thời — thứ tự khác nhau ở mỗi node là OK
Không cần đồng hồ chung cả thế giớiKhông cần total order khớp thời gian thực
Đọc câu hỏi rồi mới viết trả lời"Đã thấy A" là điều kiện để B phụ thuộc A
💡 Cách nhớ

Causal = "hậu quả không bao giờ trước nguyên nhân". Cái gì gây ra cái gì thì giữ thứ tự; cái gì độc lập thì kệ. Không đòi mọi người thấy cùng lúc — chỉ đòi đúng nhân quả.

2. Định nghĩa chính xác — happened-before, không total order

Causal consistency đảm bảo: nếu thao tác A happened-before B (A "xảy ra trước và có thể ảnh hưởng" B — ví dụ cùng một client làm A rồi B, hoặc một client đọc kết quả của A rồi mới làm B), thì mọi node quan sát A trước B. Với các cặp concurrent (không có quan hệ happened-before theo chiều nào), các node được tự do sắp xếp khác nhau.

So với linearizability (bài 01), điểm khác cốt lõi:

  • Linearizability đòi một total order (mọi thao tác nằm trên một dòng thời gian duy nhất) và total order đó khớp real-time → có recency.
  • Causal chỉ đòi một partial order (chỉ sắp các cặp nhân quả) và không dính real-time → không recency. Một node hoàn toàn có thể chưa thấy ghi mới nhất của node khác, miễn là khi nó thấy, thứ tự nhân quả vẫn đúng.

Chính vì thả cặp concurrent và bỏ real-time mà causal consistency không cần một điểm phối hợp toàn cục cho mỗi thao tác — đó là gốc của việc nó vừa khả dụng khi partition vừa rẻ.

3. Cơ chế — theo dõi nhân quả bằng vector clock

Làm sao một node biết B phụ thuộc A để mà giữ thứ tự, khi không có đồng hồ vật lý tin cậy? Câu trả lời là theo dõi quan hệ phụ thuộc một cách tường minh bằng dấu vết logic — và công cụ chuẩn là vector clock.

Nhớ lại vector clock trước khi đọc tiếp
Nhớ lại Vector clock

Vector clock gán cho mỗi node một vector đếm sự kiện của tất cả node. So sánh hai vector cho biết quan hệ: nếu vector của A nhỏ-hơn-hoặc-bằng vector của B từng thành phần (và khác nhau) → A happened-before B; nếu không so sánh được (mỗi cái lớn hơn ở một thành phần) → hai sự kiện đồng thời. Đây chính là cách máy phân biệt "nhân quả" với "độc lập" mà không cần đồng hồ vật lý.

Cơ chế ở mức ý tưởng: mỗi ghi mang theo vector clock ghi lại "tôi đã thấy những gì trước khi tạo ra mình". Khi một node nhận một ghi để áp dụng, nó kiểm: mọi thứ mà ghi này phụ thuộc đã được áp dụng chưa? Nếu chưa (một dependency còn thiếu), nó hoãn (buffer) ghi đó lại cho tới khi dependency tới — nhờ vậy không bao giờ áp "hậu quả" trước "nguyên nhân".

Node nhan mot ghi W (kem vector clock cua W):
  for each dependency D ma W phu thuoc (theo vector clock):
    if D chua duoc ap dung tai node nay:
      buffer W lai, cho D toi
  ap dung W khi moi dependency da co
// Cap concurrent: khong co rang buoc -> ap theo thu tu nao cung duoc
flowchart LR
  A["A: 'Ai biet chi minh voi?'"] -->|"nhan qua: B doc A roi tra loi"| B["B: 'Co toi!'"]
  A --> N["Node moi: buffer B<br/>cho toi A truoc"]
  C["C: like doc lap"] -.->|"concurrent voi A/B"| N2["thu tu tuy y"]

Điểm mấu chốt: node chỉ cần biết quan hệ phụ thuộc cục bộ (đọc từ metadata đi kèm mỗi ghi), không cần hỏi ý kiến cả hệ hay chờ một leader duy nhất sắp thứ tự. Đó là vì sao causal consistency chạy được kể cả khi một phần hệ bị cắt — mỗi node vẫn phục vụ đọc/ghi từ dữ liệu nó có, chỉ cần tôn trọng thứ tự nhân quả của những gì nó đã thấy.

Thử đoán trước

Ba node, ban đầu x = 0. Trên node 1: một client làm write(x=1) rồi (cùng client, đọc kết quả xong) làm write(x=2) — nên x=1 happened-before x=2. Node 3 áp dụng hai ghi này và, do mạng, nhận x=2 tới trước x=1.

Dưới causal consistency, node 3 được phép để một client đọc thấy chuỗi 2 rồi 1 (tức thấy x=2 trước, sau đó "lùi" về x=1) hay không? Và dưới linearizability thì sao? Lần theo cơ chế buffer + định nghĩa happened-before ở mục 2-3 trước khi đọc tiếp.

Lần theo cơ chế: vì x=1 happened-before x=2, chúng không đồng thời — causal consistency buộc mọi node áp dụng x=1 trước x=2. Node 3 nhận x=2 trước, nhưng vector clock của x=2 khai rằng nó phụ thuộc x=1; node phát hiện dependency x=1 chưa tới, nên buffer x=2 lại và chờ. Client trên node 3 do đó không bao giờ thấy 2 rồi 1 — nó thấy 1 rồi 2 (hoặc chưa thấy gì). Vậy cả causal lẫn linearizability đều CẤM thứ tự "2 rồi 1" ở đây, vì đây là quan hệ nhân quả, không phải đồng thời. Khác biệt giữa hai mô hình chỉ lộ ra ở cặp concurrent và ở recency: linearizability còn buộc node 3 thấy giá trị mới nhất ngay theo thời gian thực; causal thì cho phép node 3 tụt lại (chưa thấy x=2) miễn thứ tự nhân quả của những gì nó đã thấy vẫn đúng.

4. Linearizability và causal khác nhau ở đâu?

Đối chiếu bốn trục:

TrụcLinearizability (bài 01)Causal consistency
Thứ tự đảm bảoTotal order khớp real-timePartial order — chỉ cặp nhân quả
Recency (thấy giá trị mới nhất)Không — node có thể tụt lại
Cặp đồng thời (concurrent)Vẫn phải có một thứ tự chungĐược thấy khác nhau ở mỗi node
Khả dụng khi partitionKhông (phải từ chối để giữ recency) — mức mạnh nhất còn khả dụng
Chi phíPhối hợp toàn cục mỗi thao tác (chậm)Theo dõi dependency cục bộ (rẻ)

Dòng quan trọng nhất: khả dụng khi partition. Có một kết quả lý thuyết (Attiya, Ellen & Morrison, 2015) rằng causal consistency là mức nhất quán mạnh nhất mà một hệ vẫn có thể luôn khả dụng dưới partition. Muốn mạnh hơn (recency/total order) là chạm trần CAP — phải hy sinh khả dụng (bài 03). Đó là lý do causal consistency được xem là "điểm ngọt": mạnh vừa đủ để hành vi hợp lý (không có hậu-quả-trước-nguyên-nhân), yếu vừa đủ để không phải trả giá availability.

5. Pitfall — tưởng causal là recency, hoặc tưởng eventual là causal

Pitfall — nhầm cả hai phía của causal
❌ "Causal consistency => doc luon thay ghi moi nhat"
   -> SAI: causal KHONG co recency. Node co the tut lai, chua thay ghi
   moi nhat cua node khac. No chi hua: nhung gi ban DA thay dung thu tu
   nhan qua. Can recency thi phai len linearizability (dat hon).

❌ "Eventual consistency la du, no cung giu thu tu"
   -> SAI: eventual chi hua "cuoi cung hoi tu", KHONG hua thu tu nhan qua
   giua chung -> co the thay tra loi truoc cau hoi. Causal manh hon eventual
   dung o cho: them dam bao happened-before.

Hệ quả: lẫn "causal = recency" khiến đội tin dữ liệu luôn mới nhất rồi build ràng buộc cứng (khoá, uniqueness) trên causal → sai. Lẫn "eventual = causal" khiến chấp nhận eventual store rồi ngạc nhiên khi UI hiện phản hồi trước bài gốc.

Hướng đúng: xếp ba mức theo thang. Eventual (chỉ hội tụ) < causal (thêm thứ tự nhân quả) < linearizable (thêm total order + recency). Chọn mức thấp nhất đủ dùng: cần "không hậu-quả-trước-nguyên-nhân" → causal; cần "một sự thật mới nhất duy nhất" (khoá, leader) → linearizable; chịu được trễ + không quan hệ nhân quả → eventual là đủ.

6. 📚 Đào sâu — nguồn gốc & tài liệu

📚 Đào sâu — Causal consistency

Ghi chú: happened-before của Lamport là điều kiện đủ để có ảnh hưởng nhân quả, không phải nhân quả vật lý thật; hệ chỉ cần tôn trọng partial order này là đủ cho hành vi hợp lý — rẻ hơn nhiều so với ép total order.

7. Liên hệ các bài khác

  • Bài 01 — Linearizability: mức trên causal một bậc — thêm total order + recency, đổi bằng khả dụng khi partition. Đọc để thấy chính xác causal "bỏ bớt" cái gì.
  • Bài 03 — CAP đúng nghĩa: "mạnh nhất còn khả dụng khi partition" ở mục 4 là hệ quả trực tiếp của CAP — bài 03 hình thức hoá vì sao không thể mạnh hơn mà vẫn khả dụng.
  • Module 5 — Causal order: happened-before và vector clock được mổ ở đó như công cụ; bài này dùng chúng để xây một mô hình nhất quán.
  • Module 2 — Leaderless & phát hiện ghi đồng thời: version vector xử lý ghi đồng thời — cùng cơ chế nền của causal consistency.

8. Tóm tắt

  • Causal consistency giữ đúng thứ tự các thao tác có quan hệ nhân quả (happened-before); cặp đồng thời được thấy theo thứ tự khác nhau ở mỗi node.
  • Yếu hơn linearizability: chỉ partial order, không khớp real-time, nên không đảm bảo recency (node có thể tụt lại).
  • mức nhất quán mạnh nhất vẫn khả dụng khi mạng phân mảnh — đó là "điểm ngọt" khả dụng/nhất quán.
  • Cơ chế: theo dõi dependency bằng vector clock; node buffer một ghi cho tới khi mọi thứ nó phụ thuộc đã áp dụng → không bao giờ hậu-quả-trước-nguyên-nhân.
  • Rẻ hơn linearizability vì chỉ cần dependency cục bộ, không cần điểm phối hợp toàn cục mỗi thao tác.
  • Thang mức: eventual < causal < linearizable — chọn mức thấp nhất đủ dùng.

9. Tự kiểm tra

Tự kiểm tra
Q1
Causal consistency giữ thứ tự cho loại cặp thao tác nào, và thả tự do cho loại nào? Vì sao chính điều này khiến nó khả dụng được khi mạng phân mảnh?

Nó giữ đúng thứ tự cho các cặp có quan hệ nhân quả (A happened-before B — cùng client làm A rồi B, hoặc B được tạo sau khi đọc A). Nó thả tự do cho các cặp đồng thời (concurrent, không ai gây ra ai) — các node được sắp khác nhau.

Vì chỉ ép một partial order dựa trên dependency cục bộ (đọc từ metadata mỗi ghi), một node không cần hỏi ý kiến toàn hệ hay chờ một điểm phối hợp duy nhất cho mỗi thao tác. Kể cả khi bị cắt khỏi phần còn lại, nó vẫn phục vụ đọc/ghi từ dữ liệu mình có, chỉ cần tôn trọng thứ tự nhân quả của những gì đã thấy — nên vẫn khả dụng.

Q2
Vì sao causal consistency KHÔNG đảm bảo recency (thấy giá trị mới nhất), trong khi linearizability thì có? Nêu một hệ quả thực tế của việc thiếu recency.

Vì causal chỉ đòi partial orderkhông dính real-time: nó không buộc mọi node nằm trên một dòng thời gian chung khớp đồng hồ tường. Một node hoàn toàn có thể chưa nhận/áp dụng ghi mới nhất của node khác và vẫn "hợp lệ", miễn thứ tự nhân quả của những gì nó đã thấy đúng. Linearizability thì buộc total order khớp real-time → đã ghi xong là mọi đọc sau thấy ngay.

Hệ quả thực tế: sau khi một node cập nhật giá trị, một người dùng đọc ở node khác có thể còn thấy giá trị cũ trong một khoảng — chấp nhận được cho feed/like, nhưng không đủ cho ràng buộc cứng như khoá hay uniqueness (cần recency thật → phải dùng linearizability).

Q3
Node nhận một ghi B nhưng B phụ thuộc một ghi A mà node chưa áp dụng. Cơ chế causal (dùng vector clock) làm gì, và điều đó ngăn hiện tượng nào?

Node hoãn (buffer) B lại thay vì áp dụng ngay. Vector clock đi kèm B khai rõ B phụ thuộc những gì; node so sánh và thấy dependency A chưa có, nên giữ B trong buffer cho tới khi A tới và được áp dụng, rồi mới áp B.

Điều này ngăn hiện tượng "hậu quả xuất hiện trước nguyên nhân" — ví dụ một client thấy câu trả lời B trước câu hỏi A mà nó phản hồi. Nhờ buffer theo dependency, mọi node luôn áp các thao tác nhân quả đúng thứ tự, dù mạng giao gói tin lộn xộn.

Q4
Xếp ba mức eventual, causal, linearizable theo độ mạnh và nói mỗi mức thêm gì so với mức dưới. Chọn mức nào cho: (a) đồng bộ giỏ hàng giữa các thiết bị, (b) khoá phân tán?

Eventual < causal < linearizable. Eventual chỉ hứa "cuối cùng mọi bản hội tụ". Causal thêm đảm bảo thứ tự nhân quả (không hậu-quả-trước-nguyên-nhân). Linearizable thêm nữa total order khớp real-time (recency — thấy giá trị mới nhất).

(a) Giỏ hàng: causal là hợp lý — thao tác của cùng người dùng (thêm rồi xoá món) phải giữ đúng thứ tự, nhưng không cần recency tuyệt đối giữa thiết bị; eventual đơn thuần có thể cho thứ tự lộn xộn. (b) Khoá phân tán: linearizable — cần một sự thật mới nhất duy nhất ("ai đang giữ khoá") để hai node không cùng vào; causal/eventual không đủ vì thiếu recency.

Q5
Vì sao causal consistency mạnh hơn eventual consistency? Cho một hành vi mà eventual cho phép còn causal thì cấm.

Vì causal thêm một đảm bảo mà eventual không có: tôn trọng quan hệ happened-before. Eventual chỉ hứa các bản sao cuối cùng hội tụ về cùng giá trị, không nói gì về thứ tự quan sát trong lúc chưa hội tụ.

Hành vi eventual cho phép nhưng causal cấm: node hiển thị câu trả lời "Có tôi!" trước câu hỏi "Ai biết chỉ mình với?" mà nó phản hồi. Dưới eventual, gói tin tới lộn xộn nên có thể áp trả-lời trước câu-hỏi (rồi mới hội tụ). Dưới causal, vì trả-lời happened-after câu-hỏi, node buộc buffer để áp đúng thứ tự — hiện tượng đó không bao giờ xảy ra.

Bài tiếp theo: CAP đúng nghĩa — P bắt buộc, chọn C hay A

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên