TL

論理時計:Lamportタイムスタンプとベクタークロック

分散システムで「どっちが先か」を物理時刻に頼らず正しく判定。因果順序を捉えるLamport時計とベクタークロックの数理を、happened-before関係から原理で理解できます。

応用論理時計分散システムベクタークロック因果順序Lamport最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.物理時計はノード間でずれ、巻き戻りも起きるため、分散環境のイベント順序付けには使えない。代わりに論理時計で因果順序を捉える。
  • 2.Lamport時計はカウンタ1つで因果を半順序として保存するが、a<bでも因果関係があるとは限らず、並行(concurrent)を判定できない。
  • 3.ベクタークロックはノード数分の配列を持ち、VC比較で「因果あり」と「並行」を厳密に区別できる。コストは配列サイズに比例する。

なぜ物理時計では順序を決められないのか

分散システム(/devops/microservices/)で「イベントAとBはどちらが先に起きたか」を判定したくなる場面は多い。たとえば同じデータへの2つの更新が競合したとき、どちらを最終値にするかを決めるには順序が要る。素朴には各ノードの壁時計(wall clock)のタイムスタンプを比較すればよさそうに見えるが、これは破綻する。

理由は2つ。第一に、ノードごとの物理時計は 必ずずれる(clock skew)。NTP で同期しても数ミリ秒〜数十ミリ秒の誤差は残り、その間に起きた2イベントの大小はあてにならない。第二に、物理時計は 巻き戻る。NTP の補正やうるう秒で時刻が後退すると、「後のイベントの方が小さいタイムスタンプ」という逆転が起きる。

壁時計の比較は「たまたま当たる」だけ

2つのノードのタイムスタンプを比べて t_A < t_B だったとしても、それは「A が因果的に B より前」を意味しない。誤差より短い間隔のイベントでは順序が反転しうる。物理時刻に依存した順序判定は、運が良ければ当たる程度の信頼性しかない。

happened-before 関係:因果の定義

そこで Lamport(1978)は、物理時刻を捨て、イベント間の因果関係そのもの を定義した。これが happened-before 関係(記号 →)である。次の3条件で定める。

  1. 同一ノード内:同じプロセス内でイベント a が b より前に実行されたなら a → b
  2. メッセージ:a が「送信」、b がその「受信」なら a → b(送る前に受け取ることはない)。
  3. 推移律a → b かつ b → c なら a → c

この → が「a は b に影響を与ええた」という因果の可能性を表す。重要なのは、a → b でも b → a でもないペアが存在することだ。これを 並行(concurrent、記号 a ∥ b) と呼ぶ。並行とは「互いに情報が伝わっていない=どちらが先と言う意味がない」状態であり、矛盾ではなく 分散系の本質 である。→ は全順序ではなく 半順序(partial order) になる。

Lamport タイムスタンプ:半順序を数値に埋め込む

Lamport 時計は、各ノードが整数カウンタ C を1つ持ち、次の規則で更新する。

# 各ノードはローカルカウンタ C を持つ(初期値 0)
# 1) ローカルイベントが起きるたび:
C = C + 1

# 2) メッセージ送信時: まず C をインクリメントし、その値を添付して送る
C = C + 1
send(msg, timestamp = C)

# 3) メッセージ受信時: 自分の C と受信値の大きい方を取り、さらに +1
C = max(C, msg.timestamp) + 1

この規則が保証するのは 時計条件(clock condition)

a → b  ならば  C(a) < C(b)

つまり因果があれば必ずタイムスタンプは増える。受信時に max を取るのが肝で、これにより送信イベントの値が受信側へ「伝播」し、因果の鎖に沿って単調増加する。

逆は成り立たない(ここが最大の落とし穴)

C(a) < C(b) だからといって a → b とは限らない。並行な2イベントにも Lamport 時計は別々の値を割り当てるため、無関係なのに大小がついてしまう。Lamport 時計だけでは「因果あり」と「並行」を区別できない。

実用では、(C, ノードID) の辞書順で全順序(total order)を作れる。同じ C 値のときノードIDで決着をつける手法で、これを Lamport 全順序 と呼ぶ。分散ロックやトランザクションの順序付けに使えるが、あくまで「一貫した任意の順序」であって因果を完全に反映するわけではない点に注意する。

ベクタークロック:並行性を検出する

「因果あり」と「並行」を厳密に判定したいなら ベクタークロック(vector clock) を使う。N ノードの系で、各ノードは長さ N の整数配列 V(各要素が各ノードでの観測カウント)を持つ。

# ノード i の配列を V_i とする(初期は全要素 0)
# 1) ローカルイベント: 自分の要素だけ +1
V_i[i] = V_i[i] + 1

# 2) 送信時: 上記で +1 した後、配列 V_i 全体を添付して送る
# 3) 受信時: 要素ごとに max を取り、最後に自分の要素を +1
for k in 0..N-1:
    V_i[k] = max(V_i[k], msg.V[k])
V_i[i] = V_i[i] + 1

比較規則は次の通り。2つのベクタ VAVB について、

VA ≤ VB  ⇔  すべての k で VA[k] ≤ VB[k]
VA < VB  ⇔  VA ≤ VB かつ ある k で VA[k] < VB[k]

これを使うと因果関係が 過不足なく 判定できる。

判定条件意味
a → b(因果あり)VA < VBa は b に影響を与ええた
b → a(因果あり・逆)VB < VAb が a より前
a ∥ b(並行)VA ≮ VB かつ VB ≮ VA互いに未到達。順序なし
a と b が同一VA = VB同じイベント

Lamport 時計との決定的な違いは、並行を「どちらにも ≤ が成り立たない」状態として明示的に検出できる ことだ。たとえば VA = (2,1,0)VB = (1,2,0) は、1要素目は A が大きく2要素目は B が大きいので、どちらの方向にも < が成り立たない。これが並行の数学的なシグネチャである。

どちらを使うか:コストと用途

ベクタークロックは万能に見えるが、配列がノード数 N に比例して大きくなる。ノードが動的に増減する系(メッセージ1本ごとに N 要素を運ぶコスト)では重い。一方 Lamport 時計は整数1つで済むが並行を見分けられない。

観点Lamport時計ベクタークロック
保持データ整数1つ長さNの整数配列
保証a→b ⇒ C(a)<C(b)(一方向)a→b ⇔ VA<VB(双方向)
並行の検出不可可能
全順序付け(C, ノードID)で可能そのままでは半順序
コストO(1)O(N)

実務での使い分けは明確だ。全順序のチケット番号 が欲しい(分散ロック、ログのシーケンス付け)だけなら Lamport で十分。競合を検出して両方残したい(Dynamo 系 KVS の兄弟バージョン、CRDT、Git のマージ判定)ならベクタークロックが要る。

トレースの trace_id との違い

分散トレーシング(/devops/distributed-tracing/)の親子スパンも因果を表すが、あれは「1リクエスト内の呼び出し木」を記録するもの。論理時計は、独立に動く複数イベントの大域的な因果半順序 を扱う点が異なる。両者は補完的だ。

まとめ

  • 物理時計はずれと巻き戻りのため、分散環境のイベント順序付けには使えない。代わりに 因果(happened-before, →) を直接定義し、論理時計で表す。
  • Lamport 時計 は整数1つで a → b ⇒ C(a) < C(b) を保証するが逆は成り立たず、並行を区別できない(C, ノードID) で全順序は作れる。
  • ベクタークロック は長さ N の配列で a → b ⇔ VA < VB を双方向に保証し、並行(どちらにも ≤ が立たない)を厳密に検出 できる。コストは O(N)。
  • 全順序のシーケンスが欲しいなら Lamport、競合検出が要るならベクタークロック。観測(/devops/observability/)やログ突き合わせ(/devops/log-aggregation/)でも、物理時刻の限界を意識することが正確な因果分析の前提になる。

DevOps/インフラ Article

論理時計:Lamportタイムスタンプとベクタークロックを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

論理時計

比較で見る軸

難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 5

導入後に効く点

Lamport時計はカウンタ1つで因果を半順序として保存するが、a<bでも因果関係があるとは限らず、並行(concurrent)を判定できない。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
DevOps/インフラ
タグ数
5

判断チェックリスト

  • 自社の用途が「論理時計 / 分散システム」に近いか確認する。
  • 強みである「物理時計はノード間でずれ、巻き戻りも起きるため、分散環境のイベント順序付けには使えない。代わりに論理時計で因果順序を捉える。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

論理時計分散システムベクタークロック因果順序Lamport論理時計分散システムベクタークロック