為什麼不能用 Wall Clock

機器的時鐘不可信:

  • NTP 同步有誤差(通常幾毫秒到幾百毫秒)
  • 夏令時調整、閏秒
  • 虛擬機器 clock drift

兩個事件在不同機器上「幾乎同時」發生——你無法用時間戳判斷誰先。

更根本的問題:我們需要的不是「絕對時間」,而是「因果關係」——A 寫入發生在 B 讀取之前嗎?這筆更新是在那個版本之後嗎?


Lamport Clock

Leslie Lamport 在 1978 年提出,定義 happens-before(→)關係

  1. 同一個 process,事件 a 在 b 之前 → a → b
  2. process i 發送訊息 m,process j 收到 m → send(m) → receive(m)
  3. 傳遞性:a → b,b → c → a → c

Lamport Clock 的規則

  • 每個事件遞增自己的 counter
  • 發送訊息時帶上自己的 counter
  • 收到訊息時:clock = max(own_clock, received_clock) + 1
Process A: 1  2  3
              \
Process B: 1  3  4  5
              ↑
         收到 A 的 counter=2,
         自己的 1 < 2,所以更新為 2+1=3

問題:Lamport Clock 只能判斷「可能的因果關係」,不能判斷「沒有因果關係」。counter(A) < counter(B) 不代表 A → B,只代表「如果 A → B,那麼 counter(A) < counter(B)」。


Vector Clock

Vector Clock 解決了 Lamport Clock 的弱點,能判斷「concurrent(沒有因果關係)」:

每個 process i 維護一個向量 V[n](n = 節點數量):

規則

  1. 內部事件:V_i[i] += 1
  2. 發送訊息:V_i[i] += 1,然後發送整個向量
  3. 收到訊息:V_i[j] = max(V_i[j], V_received[j]) for all j,然後 V_i[i] += 1

比較規則

  • A → B(A happens before B):A 的所有 element B 的,且至少一個 <
  • A concurrent B:A 和 B 都不 happens-before 對方
Node A starts: [1,0,0]
Node B starts: [0,1,0]
Node C starts: [0,0,1]

Node A sends to B:  A=[2,0,0], B receives → B=[2,2,0]
Node B sends to C:  B=[2,3,0], C receives → C=[2,3,1]
Node A sends to C:  A=[3,0,0], C receives → C=[3,3,2]

現在:
A 的 [3,0,0] vs B 的 [2,3,0]
→ A[0]=3 > B[0]=2,但 A[1]=0 < B[1]=3
→ 無法比較,是 concurrent

實際應用

Amazon Dynamo:使用 Vector Clock 追蹤 key 的版本,當兩個 replica 各自更新同一個 key 時,用 Vector Clock 偵測「這兩個更新是否有因果關係」,如果是 concurrent → 衝突,需要 client-side conflict resolution(或 Last-Write-Wins)。

分散式資料庫的 CRDT(Conflict-free Replicated Data Types):Causal Consistency 的底層需要 Vector Clock 語義。

Git:類似 Vector Clock 概念——每個 commit 指向父 commit,形成 DAG(有向無環圖),解決「這兩個 branch 改的是不是同一個版本的後代」問題。


簡化版本:版本向量(Version Vector)

實務上很多系統用「版本向量」(Version Vector)而不是完整的 Vector Clock——只追蹤每個 replica 最後寫入的 counter,不追蹤每個操作。語義略有不同,但開銷更小,足以處理大多數 conflict detection 場景。