このページの目次
時間とクロック
分散システムにはグローバルクロックが存在しない——2つのノード上のタイムスタンプを直接比較することはできない。Lamport論理クロック(happens-before)からベクトルクロック(並行性の検出)、そしてTrueTime(GPS+原子時計)に至るまで、それぞれの「クロック」は、一見単純な「イベントの先后」という問題を、異なるコストのトレードオフの中で追求している。
問題: グローバルクロックの欠如
単一マシン上では gettimeofday() を用いて、イベントAとBの先后を比較できる。しかし分散システムでは、Aがノード1で 12:00:00.000 に発生し、Bがノード2で 12:00:00.001 に発生した場合——Aが先であると確定できるだろうか?できない。なぜなら、2つのノードのクロックが完璧に同期することは不可能だからだ。NTPによる同期精度は約1msであり、1ms以内のイベント順序は物理時間だけでは判断できない。
Lamport論理クロック (1978)
Leslie Lamportの洞察:因果関係を判断するために物理時間が必要なのではない——重要なのは、happens-before関係がタイムスタンプに反映されることを保証することだ。
アルゴリズム
各ノードは整数のカウンター C を維持し、初期値は0とする:
- ローカルイベントが発生(メッセージ送信を含む):
C = C + 1、イベントのタイムスタンプ = C - メッセージを送信する際、自身の現在の C を添付する
- メッセージを受信(タイムスタンプ = C_msg):
C = max(C, C_msg) + 1、受信イベントのタイムスタンプ = C
性質:a が b より前に発生する場合 (a happens-before b)、C(a) < C(b) となる。しかし逆は成り立たない——C(a) < C(b) であっても a → b とは限らない(並行イベントがたまたま異なるカウンター値を持つ可能性がある)。
分散排他制御アルゴリズム (Lamport Mutex)
論理クロックを用いて、セントレス(中央ノードなし)の排他制御を実現する:
- ノード i が臨界領域に入る場合:全ノードに
REQUEST(ts_i, i)を送信 - リクエストを受信したノード j:臨界領域にいない、かつ入る予定もない場合、
REPLYを返信 臨界領域にいる、または自分のリクエストの優先度が高い場合 (ts_j < ts_i) → 返信を遅延させる - ノード i が全 REPLY を受信後 → 臨界領域に入る
- 臨界領域から退出 → 全ノードに
RELEASEを送信
利点:単一障害点がなく、完全に分散されている。欠点:3(N-1) 件のメッセージが必要であり、単一ノードの障害がシステム全体のブロックを引き起こす可能性がある。
ベクトルクロック
Lamportクロックの限界:C(a) < C(b) から a → b を逆推論できない。ベクトルクロックはこの問題を解決する。
データ構造
各ノードは長さ N のベクトル V[1..N] を維持する。V[i] は、ノード i が何回イベントを発生させたか、そのノードが把握している値である。
V_a = [3, 5, 2] 意味: ノード1は、自身で3つのイベント、ノード2で5つ、ノード3で2つのイベントが発生したことを知っている
アルゴリズム
- ノード i でイベントが発生:
V[i] = V[i] + 1 - メッセージを送信する際、現在の V を添付する
- メッセージを受信(添付された V_msg):
V[k] = max(V[k], V_msg[k]) for all k、その後V[i] = V[i] + 1
比較ルール
V(a) < V(b)(a が b より前に発生): V(a) の各要素が V(b) の対応する要素以下であり、かつ少なくとも1つが厳密に小さいV(a)とV(b)は比較不可:k, j に対して V(a)[k] < V(b)[k] かつ V(a)[j] > V(b)[j] となる場合 → 並行イベント
実践的な応用
Dynamo/Riak KV はベクトルクロックを用いて競合を検出する:get(key) がベクトルクロック V_old を返す際、クライアントが修正後 put(key, value, V_old) を実行する。サーバー上の現在のバージョンが V_cur の場合、V_cur と V_old を比較する:
V_cur < V_old: 直接上書き(クライアントが現在のバージョンを確認して修正した)V_cur > V_old: 拒否(クライアントが古いバージョンに基づいて修正した)→ read-modify-write が必要- 比較不可: 並行修正 → 競合解決が必要(アプリケーション層でのマージ、または複数のバージョン(sibling)の保持)
Google TrueTime (Spanner)
最も過激な産業実践:原子時計とGPSを用いて、グローバルなデータセンターに有界なクロックの不確実性区間を提供する。
TT.now() は区間 [earliest, latest] を返す
保証:真の時間は必ずこの区間に含まれる。区間の幅は通常約1〜7ms(GPS/原子時計の同期状態による)。
Spanner の外部一貫性
Spanner のトランザクションは、TrueTime を用いて最も強力な外部一貫性(linearizability よりも強い——データセンター間のリアルタイム順序)を保証する:
コミット待機: トランザクションのコミットタイムスタンプ = TT.now().latest
トランザクションがレプリカで有効になる前、TT.after(commit_ts) = True となるまで待機
→ 全ノードが見るコミット順序が真の時間と一致することを保証
なぜハードウェアが必要なのか
ソフトウェアでは、有界な誤差を持つクロックを提供することは不可能である——NTP は誤差を見積もるのみであり、ネットワーク遅延の非対称性とジッターにより、誤差の境界は不明瞭になる。TrueTime の保証は、GPS/原子時計の物理的特性に由来する:原子時計の周波数誤差は 10^-13 未満である。
NTP/PTP の実践
NTP (Network Time Protocol): UDPパケットの交換を通じてサーバーとの時間偏差を見積もる。精度は約1ms(LAN)、約10ms(WAN)。課題:
- ジャンプ: NTP が大きな偏差を突然補正 → クロックが巻き戻る →
CLOCK_MONOTONICはこのジャンプを露呈するが、壁掛け時計の時間は遡る - 振動: NTP が継続的に補正 → クロックの周波数が継続的に微小に変化 → 単調時間を依存するPrometheus TSDB が影響を受ける可能性がある(ただしPrometheusはスクレイピングタイミングに
CLOCK_MONOTONICを使用) - 同期失敗: NTPサーバーに到達不可 → クロックのドリフトが蓄積 → 数時間〜数日後には誤差が秒単位になる
PTP (Precision Time Protocol, IEEE 1588): データセンターレベルの時間同期、精度は約100ns。ハードウェアサポート(NICのタイムスタンプ機能)が必要。金融取引(MiFID II 準拠)や Spanner の代替クロックソースに使用される。
参考
- 論文: "Time, Clocks, and the Ordering of Events in a Distributed System" (Lamport, 1978)
- 論文: "Spanner: Google's Globally-Distributed Database" (2012, TrueTime セクション)
- 論文: "Virtual Time and Global States of Distributed Systems" (Mattern, 1989 — ベクトルクロック)
Keywords: Lamport clock, vector clock, happens-before, TrueTime, Spanner, NTP, PTP, logical time