只保住「有因果關係的操作」彼此的順序,放掉「無關操作」的全域即時排序。它是在網路分區下仍能維持高可用的最強一致性模型——拿到了線性一致八成的好處,卻不必付全域協調的代價。
🧠 Intuition
TIP
因果一致 = 「只排該排的順序」。如果 B 操作能「看見」A(讀到 A 寫的值、或在同一個 client 接續 A 之後),那所有人都必須先看到 A 再看到 B;但兩件並發 (concurrent)、互不知情的操作,誰先誰後可以各副本自行決定。
- 核心是 happens-before 偏序 (partial order),不是線性一致那種全序 (total order)。
- 它不要求即時性 (recency):你可能讀到舊值,只要這個舊值不違反你已經看過的因果鏈即可。
- 「問題 → 回答」「貼文 → 留言」這類因果不能倒置;但兩則互不相干的貼文出現先後可以人言人殊。
- 之所以重要:因果一致是不被分區拖垮的上限——CAP 下仍可用,再往上(線性一致)就得犧牲可用性。
⚖️ Tradeoffs
因果序 vs 全序 — 為什麼放掉全序這麼關鍵
- 線性一致要把所有操作排成單一全序,這需要跨副本的全域協調(共識),分區時就掛。
- 因果一致只維護 happens-before 偏序,並發操作不需協調 → 各副本可在本地先生效、事後傳播。
- 代價:失去即時性,存在「不同 client 看到並發事件順序不同」的窗口——但絕不會看到果在因前。
為何是分區下最強的一致性
- 有個理論結果:因果一致是「在容忍分區、保持可用」前提下能達到的最強一致性模型 (Mahajan et al.)。
- 直覺:再強一點就需要對並發操作定全序,而定全序 = 需要全域同步 = 分區時要嘛阻塞要嘛拒絕服務。
- 所以實務上「想要強保證又不想犧牲可用性」的系統(如 COPS、MongoDB causal sessions)都停在這一層。
怎麼實作 — 版本向量 / Lamport timestamp
- 版本向量 (version vectors):每個副本一個計數器,能精確判斷兩事件是 happens-before 還是並發——並發就保留為衝突,交給應用解決。
- Lamport timestamp:(counter, nodeId) 給出與因果相容的全序,但無法區分「真並發」與「真有序」,只保證若 A→B 則 ts(A)<ts(B)。
- 取捨:版本向量資訊完整但大小隨副本數成長;Lamport timestamp 輕巧但會把並發強行定序、丟失並發資訊。
一致前綴讀 (Consistent Prefix Reads)
- 因果一致蘊含一致前綴讀:讀者看到的,一定是某個「合法前綴」的寫入序列,不會出現「看到答案卻看不到對應問題」。
- 違反案例多發生在分片系統:不同 partition 各自非同步複製,跨片的因果(A 在片 1、B 在片 2)若無協調就可能倒置。
- 修法:用因果序追蹤(時間戳/依賴標記)讓跨片讀也尊重 happens-before。
🔑 Takeaways
- 因果一致 = 維護 happens-before 偏序,只排有因果關係的操作,放掉並發操作的全域定序。
- 它不要求即時性,但保證你永遠不會看到「果在因前」,並蘊含一致前綴讀。
- 是容忍分區又保可用前提下能達到的最強一致性——再上一層就得犧牲可用性。
- 實作靠版本向量(精確辨並發)或 Lamport timestamp(輕量但強行定序);分片系統要特別處理跨片因果。
2026-07-01
- Q: 因果一致跟「最終一致」差在哪?兩個不是都允許讀到舊值嗎?
- A: 都允許讀到舊值,但最終一致不保證順序——它只承諾「停止寫入後副本終會收斂」,過程中你可能看到果在因前(先看到留言、後看到原貼)。因果一致額外保證 happens-before 不被違反:有因果關係的操作對所有人都同序。換句話說,因果一致是「帶順序保證的最終一致」,是最終一致之上、線性一致之下的中間檔。