- ブラウザURL入力
- DNS名前→IP
- ルータ経路選択
- ロードバランサ振り分け
- Nginx受け口
- アプリ処理今ここ
- DB保存
アプリが複数の機械に分かれた瞬間、「いつ起きたか」という素朴な問いが答えにくくなります。その理由を時計から見ます。
まず結論
複数の機械にまたがると、「どちらが先に起きたか」を教える共通の時計はありません。壁時計は比較できる形ですが合っておらず、単調時計は合っていますが原点が機械ごとなので比較に意味がありません。
残るのは、メッセージの送受信から辿れる因果関係だけです。Lamport はこれを happened-before と名付けました。ただし半順序なので、すべての事象に順番は付きません。全順序が要るなら、自分で作るものです。
NTPで時刻を合わせた2台のサーバのログを見比べれば、どちらのイベントが先に起きたか判定できるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
1台の中では自明だったものが、ここで消える
1台の中では、順序は考えなくても手に入ります。DB編『先に書くログ — 順序を縛ると、書く回数が減る』のとおり、ログは書いた順に積まれ、「どちらが先か」は同じ1本の列の中で答えられました。
2台になると、この共通の列が消えます。では時刻を使えばよいか。Lamport は1978年の論文でこう書いています。
Even if it does contain real clocks, there is still the problem that such clocks are not perfectly accurate and do not keep precise physical time. We will therefore define the "happened before" relation without using physical clocks.
物理時計は正確ではないので、それを使わずに「先に起きた」を定義する、という宣言です(p.558)。
Definition. The relation "→" on the set of events of a system is the smallest relation satisfying the following three conditions: (1) If a and b are events in the same process, and a comes before b, then a → b. (2) If a is the sending of a message by one process and b is the receipt of the same message by another process, then a → b. (3) If a → b and b → c then a → c.
同じプロセス内の前後、メッセージの送信と受信、その積み重ね。この3つだけで、時刻は出てきません。Lamport はこれを a → b means that it is possible for event a to causally affect event b と読み替え、only a partial ordering だと述べています。分かるのは因果の可能性だけで、順番が付かない組が残ります。
- ・矢印が引けないことは「同時刻」を意味しない。物理的にはどちらかが先かもしれない
- ・それを知る手立てが系の側に無い、というのが定義の主張
壁時計は巻き戻る、単調時計は巻き戻らない
man-page の2つの時計を並べます。まず CLOCK_REALTIME。
A settable system-wide clock that measures real (i.e., wall-clock) time. … This clock is affected by discontinuous jumps in the system time (e.g., if the system administrator manually changes the clock), and by frequency adjustments performed by NTP and similar applications
設定可能で不連続に飛ぶ。一方 CLOCK_MONOTONIC。
The CLOCK_MONOTONIC clock is not affected by discontinuous jumps in the system time …, but is affected by frequency adjustments. This clock does not count time that the system is suspended. All CLOCK_MONOTONIC variants guarantee that the time returned by consecutive calls will not go backwards, but successive calls may — depending on the architecture — return identical (not-increased) time values.
巻き戻らない、周波数調整の影響は受ける、サスペンド中は数えない、同じ値が返りうる。この4点が後で効きます。Docker Desktop 上の特権(--privileged)Alpine コンテナから壁時計を5分戻した記録です(2026-09-18)。time namespace は CLOCK_REALTIME を仮想化しない(time_namespaces(7))ので、戻ったのは Linux VM 全体の壁時計です(macOS 側は無変更)。
before set: REALTIME=1789688330.600524 MONOTONIC=978057.872624after set: REALTIME=1789688030.502000 MONOTONIC=978058.375555REALTIME delta = -300.099 s <-- went BACKWARDSMONOTONIC delta = +0.503 s <-- still moved forward, never backwards
同じ0.5秒の間に、壁時計は300秒戻り、単調時計は0.5秒進みました。経過時間を測るなら単調時計、という定石の根拠です。
同じ環境の clock_getres はいずれの時計も res = 0.001 s(1ミリ秒)を返しましたが、これは Docker Desktop の Linux VM 上の値です。ベアメタルの Linux では通常1ナノ秒が返ります。「Linux の時計の分解能は1ミリ秒」と一般化しないでください。
Linux の CLOCK_MONOTONIC について、man-page の記述と合っているのはどれですか。
では単調時計でノード間の順序が決まるか
決まりません。単調時計の原点は機械ごとで、Linux では起動時です。2台の値は最初から無関係な数です。
これを見せるため、片方のコンテナの原点を人為的に3600秒ずらして観測しました(unshare --time --monotonic=3600)。実機2台の起動時刻差を測ったのではなく、「原点が機械ごとである」ことを再現したものです。
NODE-A REALTIME=1789688366.609078 MONOTONIC=978093.881040NODE-A REALTIME=1789688367.212183 MONOTONIC=978094.484144NODE-B REALTIME=1789688367.526276 MONOTONIC=981694.798238NODE-B REALTIME=1789688368.138035 MONOTONIC=981695.409997
壁時計は両者ほぼ一致しています。同じホストのカーネルの壁時計を見ているからです。ここが本題で、壁時計は比較できる形だが合っておらず、単調時計は合っているが比較する意味がない。だから残るのが happened-before です。
時計を信じた設計と、時計を疑う設計
タイムスタンプの大きいほうを採る規則を LWW(last write wins)と呼びます。Apache Cassandra の公式文書はこう明記しています。
Cassandra uses mutation timestamp versioning to guarantee eventual consistency of data. Specifically all mutations that enter the system do so with a timestamp provided either from a client clock or, absent a client-provided timestamp, from the coordinator node's clock. Updates resolve according to the conflict resolution rule of last write wins. Cassandra's correctness does depend on these clocks, so make sure a proper time synchronization process is running such as NTP.
正しさが時計に依存する、と公式が認めています。しかも使われる時計は状況で変わります。クライアントがタイムスタンプを付ければその時計、付けなければコーディネータノードの時計です。
規則から筋を追うとこうなります(実クラスタの観測ではなく算術上の筋道です)。ノードBの壁時計がAより5秒遅れているとき、Aに書いた後、実時間としては後にBへ書いても、Bのタイムスタンプが小さくなる。LWW は大きいほうを残すので、後から書いたほうが捨てられ、エラーも返りません。
CockroachDB は逆に、時計が合わない前提で安全装置を置いています。
CockroachDB requires moderate levels of clock synchronization to preserve data consistency. For this reason, when a node detects that its clock is out of sync with at least half of the other nodes in the cluster by 80% of the maximum offset allowed, it spontaneously shuts down. This offset defaults to 500ms
許容最大ずれの既定は500ミリ秒(起動時のフラグで変更可)。その80%に達したノードは自分で止まります。
happened-before(a → b)が成り立つと分かったとき、言えることは何ですか。
壁時計と単調時計
壁時計は1970年起点なので比較できる形をしていますが、設定変更や調整で飛びます。単調時計は巻き戻りませんが、原点が機械ごとなので他ノードの値との引き算に意味がありません。
時計 ノード間で比較 1台の中の経過時間壁時計 形の上は可能 不向き(飛ぶ)単調時計 意味を持たない 適する
権威サーバの持つゾーンの版(DNS編)
DNS編『権威が複数ある理由 — ゾーン転送と、同じ中身を保つ仕掛け』のとおり、複数の権威は同じ番号で版を持ちます。番号が大きいほうが新しいのは、1つの発信元が振った番号だからです。
方式 番号の出どころ 大小比較の意味壁時計 各ノード 基準が揃わないゾーンの版 1つの発信元 新旧が確定する
どちらも「数を比べて新旧を決める」形です。片方は決まり、片方は決まりません。違いはどこでしょうか。
その数を誰が振ったか、を考えてみてください。
この章のまとめ
- 順序を教える共通の時計はない。壁時計は比較できる形だが合っておらず、単調時計は原点が機械ごとで比較に使えない。
- CLOCK_MONOTONIC は「一切ずれない時計」ではない。周波数調整の影響を受け、サスペンド中は進まず、保証は「減らない」だけ(同じ値が返りうる)。
- happened-before で分かるのは因果、つまり「影響しうる」かどうかだけ。Lamport はこれを only a partial ordering と呼ぶ。
- LWW は時計のずれが「どちらが残るか」に効く。CockroachDB はずれが閾値を超えたノードを自ら停止させる。
- 同一ホスト上のコンテナは既定で時計を共有する。「コンテナごとに時計が違う」は誤り。
- 全順序は作るもの。1つの発行元が振る巻き戻らない数を論理時計と呼び、本コースの後半で回収する。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
同じホスト上で2つのコンテナを動かし、両方で時刻を取ると、コンテナごとの時計のずれが観測できますか。
用語集(6語)
- 壁時計 (CLOCK_REALTIME)
- 1970年からの秒数を数える設定変更可能な時計。管理者の操作やNTPの調整で不連続に飛ぶ。ノード間で比較できる形だが、一致している保証はない。
- 単調時計 (CLOCK_MONOTONIC)
- 設定変更できず、連続する呼び出しで値が減らない時計。Linux では起動時が原点。周波数調整の影響を受け、サスペンド中は進まない。
- happened-before (→)
- Lamport 1978 の定義。同一プロセス内の前後、メッセージの送信と受信、その推移閉包で定まる。a → b は「a が b に因果的に影響しうる」ことを意味する。
- concurrent(並行)
- a → b でも b → a でもない2つの事象の関係。系の側に前後を知る術がない状態。
- 論理時計
- 物理時刻を諦め、巻き戻らない数を振って順序を作る仕掛け。本コースの後半で扱う。
- LWW (last write wins)
- 競合する更新のうちタイムスタンプが大きいほうを採る規則。時計のずれが結果に直接効く。
出典(一次ソース)
- Leslie Lamport, Time, Clocks, and the Ordering of Events in a Distributed System (CACM Vol.21 No.7, 1978) ↗
- clock_gettime(2) — Linux man-pages ↗
- time_namespaces(7) — Linux man-pages ↗
- RFC 5905 — Network Time Protocol Version 4: Protocol and Algorithms Specification ↗
- Apache Cassandra Documentation — Dynamo (Architecture) ↗
- CockroachDB Docs — Operational FAQs ↗