Global State and Distributed Snapshots

Imagine a banking system spread across ten servers. Each holds account balances and passes "transfer" messages over the network. To audit the system, someone must ask a key question. What is the total money in the bank right now? But there is no "now" in a distributed system. There is no shared clock, no single observer. There is no way to freeze every machine simultaneously. Suppose you ask each server to report its balance at slightly different real times. In-flight transfer messages can be double-counted or lost entirely. As a result, the audit will not balance. This is the problem of recording a global state. It means capturing a snapshot of every process's local state plus every message in transit. This must happen in a system with no global clock and unpredictable message delays. It matters far beyond banking. Detecting deadlocks in distributed resource graphs requires it. Checkpointing long-running computations for fault tolerance requires it too. So does garbage-collecting distributed objects. Verifying stable properties, like "the system has terminated," also requires it. All of these need a consistent picture of a system that never stops moving. The elegant answer comes from K. Mani Chandy and Leslie Lamport in 1985. They showed you do not need synchronized clocks at all. You need only a careful protocol using special marker messages. These messages ride along the existing communication channels. This is combined with the notion of a consistent cut. A consistent cut is a slice through the system's history that respects causality. The snapshot the algorithm produces may never have existed as an actual instant in real time. Yet it is guaranteed to be a state the system could have passed through. That guarantee is precisely what makes it useful. This article develops the theory of global states and consistent cuts. It walks through the Chandy-Lamport snapshot algorithm in full pseudocode. It proves why the resulting snapshot is consistent. It traces its consequences for deadlock detection and checkpointing. Finally, it explores the broader theory that leads into Consensus Problem and Distributed Transactions and Two-Phase Commit.

Key takeaways
  • A global state of a distributed system is the collection of all local process states plus all messages currently in transit on every channel.
  • A cut is a way of picking one local "moment" per process; a cut is consistent if it never includes the effect of a message without including its cause (no message received before it was sent, in cut terms).
  • The Chandy-Lamport algorithm records a consistent global snapshot using marker messages on FIFO channels, without any global clock or process pausing.
  • Each process records its own state on first receiving a marker, then records incoming channel messages until markers arrive on all channels.
  • The algorithm runs in O(E)O(E) messages and O(N+E)O(N + E) extra messages total (markers on each of EE channels), and terminates once every process has seen a marker on every incoming channel.
  • Consistent snapshots enable stable property detection (deadlock, termination), checkpointing for recovery, and set the stage for coordination protocols like two-phase commit.
The core insight

A distributed snapshot need never have been a real instant in time — it only needs to be a causally consistent state the system could have reached, and marker messages on FIFO channels are enough to construct one without stopping the system.

The idea

A distributed system consists of NN processes communicating by message passing over channels, with no shared memory and no perfectly synchronized clocks. The global state of the system at a conceptual instant is the tuple of all local process states together with the multiset of messages currently "in flight" on each channel — sent but not yet received. Because there is no global clock, no external observer can literally look at all processes at once; any attempt to collect states by querying processes one at a time risks capturing a picture that is causally impossible, e.g., recording that process QQ has received a message from PP while PP's recorded state shows it never sent it. To make "in flight" and "consistent" precise, model the execution of the system as a set of local events (send, receive, internal) totally ordered per process, with the happened-before relation →\rightarrow from Logical Clocks and Lamport Timestamps capturing causality across processes: if aa is a send and bb the matching receive, then a→ba \rightarrow b. A cut CC is a subset of the global history that includes a prefix of each process's local events — formally, for each process ii a cut point after which no more events of ii are included. A cut is consistent if it is closed under happened-before restricted to receive-implies-send: whenever a receive event is in CC, the corresponding send event is also in CC. Equivalently, a consistent cut never "sees" the effect of a message without having "seen" its cause. The global state induced by a consistent cut is well-defined and internally coherent: for every channel, the messages "in transit" recorded by the snapshot are exactly those sent (by the sender's recorded prefix) but not yet received (by the receiver's recorded prefix). This state need not correspond to any real simultaneous instant across all machines — the local recording points can happen at wildly different real times — but it is a state that some valid re-execution of the system could pass through, which is exactly what makes properties detected on it ("no deadlock," "total money = X") trustworthy. This connects tightly to Vector Clocks, whose comparability structure captures precisely which pairs of events are causally ordered and therefore which cuts can be consistent.

How it works

The Chandy-Lamport algorithm assumes: 1. Communication channels are point-to-point, FIFO (messages arrive in the order sent), and 2. Channels are reliable (no loss, no duplication), and 3. The underlying communication graph is strongly connected (a message can reach any process from any other, possibly indirectly). The snapshot is triggered by one or more processes (an "initiator") and propagates via special control messages called markers, which are distinguishable from application messages but travel over the same channels, preserving FIFO order relative to them. The key trick: a marker on a channel acts as a divider — everything the receiver got before the marker belongs to the "before" snapshot state; everything the receiver gets after the marker (and before markers on all channels) is a message that was "in flight" at snapshot time and must be recorded as channel state.

Process-level rule, restated as the algorithm each process runs: ```pseudocode // State kept per process P recorded : boolean := false local_state : ProcessState channel_state[c] : list of messages, for every incoming channel c recording[c] : boolean := false // are we still logging channel c? // Rule 1: P initiates the snapshot (or is asked to, upon first marker) procedure INITIATE_SNAPSHOT(P): if not P.recorded: P.local_state := snapshot of P's current local state P.recorded := true for each outgoing channel c_out of P: send MARKER on c_out // before any further app messages for each incoming channel c_in of P: P.recording[c_in] := true P.channel_state[c_in] := [] // start logging (empty so far) // Rule 2: P receives a MARKER on incoming channel c procedure ON_RECEIVE_MARKER(P, c): if not P.recorded: // first marker P has seen anywhere: this triggers P's own snapshot P.local_state := snapshot of P's current local state P.recorded := true P.channel_state[c] := [] // channel c is empty in the snapshot: // marker arrived before any message did for each outgoing channel c_out of P: send MARKER on c_out for each incoming channel c_in of P, c_in != c: P.recording[c_in] := true P.channel_state[c_in] := [] else: // P already recorded; this marker closes channel c's logging window P.recording[c] := false // P.channel_state[c] now holds exactly the messages that arrived // on c strictly between P's own recording and this marker if all incoming channels have recording == false: P.SNAPSHOT_DONE() // send (local_state, channel_state[]) to collector // Rule 3: P receives an ordinary application message m on channel c procedure ON_RECEIVE_APP_MESSAGE(P, c, m): if P.recording[c]: P.channel_state[c].append(m) // log it — it was "in transit" at snapshot time deliver m to the application layer as normal ``` Step by step, the protocol unfolds as follows: 1. <strong>Initiation.</strong> One or more processes decide to start a snapshot (e.g., triggered externally, or by a monitor process). Each initiator records its own local state immediately, then sends a marker on every outgoing channel before sending any further application messages. 2. <strong>First marker received.</strong> When any process QQ receives a marker for the first time (on some channel cc), it immediately records its own local state — this is QQ's contribution to the global snapshot — and declares channel cc's state as <strong>empty</strong> (since the marker beat every application message that might have been in transit on cc, by FIFO order, and no other message can now arrive on cc before the marker). It then sends markers on all of its own outgoing channels, and begins recording all other incoming channels. 3. <strong>Recording in-transit messages.</strong> For every incoming channel not yet closed by a marker, QQ appends every application message it receives to that channel's recorded log — these are exactly the messages that were sent before the sender's snapshot point but had not yet arrived when QQ took its own snapshot. 4. <strong>Marker on an already-open channel.</strong> When QQ later receives a marker on channel cc (after QQ has already recorded), this signals that all messages sent on cc before* the sender's snapshot have now arrived. QQ closes (stops recording) channel cc; whatever accumulated in `channel_state[c]` is final. 5. Termination. Once every process has received a marker on every incoming channel, every process has recorded its local state and every channel's in-transit messages, so the entire global snapshot is complete. A designated collector (or each process independently, then merged) assembles the full picture: NN local states plus, for each of the EE directed channels, a list of in-flight messages.

Figure
Marker propagation across three processes on a ring topology
P (initiator) Q R record P record Q record R marker marker marker marker closes P's last channel app msg m (sent before P's marker) Q logs m into channel_state[P→Q] Red dots = local state recorded. Blue = marker messages. Purple dashed = in-transit app message, logged by receiver.

Complexity & correctness

Message complexity. Exactly one marker traverses each directed channel per snapshot initiation cycle — a process sends a marker on every outgoing edge exactly once (upon initiating or upon first receiving a marker). With NN processes and EE directed channels in the communication graph, the algorithm sends exactly EE marker messages in total, independent of how many application messages are in flight. No process needs to pause; application messages continue to flow and are simply logged or passed through. This gives O(E)O(E) control messages and O(1)O(1) additional messages per channel — asymptotically optimal, since every channel must be probed at least once to know it has quiesced.

Space complexity. Each process stores one local state snapshot (O(1)O(1) extra beyond its normal state) plus, per incoming channel, a list of logged in-transit messages, bounded by the number of application messages sent on that channel between the two processes' snapshot points. In the worst case this is bounded by the channel's maximum in-flight capacity (bandwidth-delay product), but is typically small in practice.

Termination. The algorithm terminates because the communication graph is strongly connected and every channel carries a bounded (eventually delivered) sequence of messages: each process sends a marker on each outgoing channel exactly once, so every process receives at least one marker on each incoming channel in finite time (assuming reliable delivery), triggering closure of every channel's recording.

Correctness — why the resulting snapshot is a consistent cut. The core invariant is: > For any channel from process AA to process BB: every application message that BB's snapshot logs as "in transit" is exactly a message that AA sent before AA's local snapshot point, and that BB received after BB's local snapshot point. This follows from three facts guaranteed by FIFO channels and the marker rule: 1. Every message AA sends after taking its snapshot is sent after AA's outgoing marker on that channel (Rule: markers are sent immediately upon recording, before any further application sends can be attributed to the post-snapshot state — more precisely, the algorithm requires the process to send its markers as its very next action after recording, which is achievable because recording is a local, atomic step). Hence any message sent after AA's snapshot arrives at BB after AA's marker, by FIFO. 2. By FIFO, BB receives AA's marker before any message AA sent after its snapshot point. So once BB sees AA's marker, every subsequent message on that channel was necessarily sent by AA after AA's snapshot — these must not be logged as in-transit (they belong to the "future" of the snapshot), and indeed Rule 2 stops logging exactly when the marker arrives. 3. Messages AA sent before its snapshot but not yet received by BB at the time BB takes its own snapshot are precisely the messages BB receives between BB's own recording point and AA's marker's arrival — and Rule 3 logs exactly these. Together these show that the recorded channel state for A→BA \to B equals precisely: {messages sent by AA before AA's cut} ∖\setminus {messages received by BB before BB's cut}. That is exactly the definition of "messages in transit across a consistent cut" for that channel. Since this holds for every channel, the overall snapshot corresponds to a globally consistent cut: no recorded local state at BB reflects the receipt of a message that the recorded state at AA shows as unsent. This is precisely the causal-consistency condition from the happened-before relation, so the assembled global state is one the system could legitimately have passed through in some interleaving — even though the local snapshot instants may be far apart in real (wall-clock) time.

Tradeoffs. The algorithm requires FIFO, reliable channels — on non-FIFO networks (e.g., UDP without sequencing) the marker trick breaks, because an application message sent after the marker could overtake it and be misclassified. It also assumes the graph is strongly connected so markers can reach every process; on a partitioned or non-strongly-connected topology, extra logical channels (or a spanning communication structure) must be assumed. The algorithm records one possible consistent global state, not necessarily the "true" simultaneous one (which may not even be a coherent concept) — for many analyses (stable predicates: predicates that, once true, remain true) this is sufficient, but transient properties cannot be safely inferred from any single snapshot.

Worked example

Consider three processes P,Q,RP, Q, R arranged so that P→QP \to Q, Q→RQ \to R, R→PR \to P, and also P→RP \to R directly (four directed channels, E=4E = 4). Suppose PP holds $70, QQ holds $50, RR holds $30 (total $150), and the following events occur in real time (unsynchronized clocks — times shown are illustrative wall-clock, not known to the processes):

TimeEvent
t1t_1PP sends $10 to QQ (app message m1m_1, on channel P→QP\to Q)
t2t_2PP initiates the snapshot: records local state ($60, since m1m_1 already debited), sends MARKER on P→QP\to Q and P→RP\to R
t3t_3QQ receives m1m_1 ($10 arrives, QQ now has $60) — this happens before QQ sees any marker
t4t_4QQ receives MARKER from PP (first marker seen): QQ records local state = $60, declares channel P→QP\to Q empty in the snapshot (marker arrived after m1m_1, so nothing further pending), sends MARKER to RR
t5t_5RR receives MARKER from PP (first marker seen): RR records local state = $30, declares P→RP\to R empty, sends MARKER to PP
t6t_6QQ sends $20 to RR (app message m2m_2, on channel Q→RQ \to R) — sent after QQ's snapshot
t7t_7RR receives m2m_2 — arrives after RR already recorded its own state at t5t_5, and before RR sees a marker on Q→RQ\to R, so RR logs m2m_2 into `channel_state[Q→R] = [$20]`
t8t_8QQ sends MARKER on Q→RQ\to R (this was already implied at t4t_4, listed here for the channel's closing)
t9t_9RR receives MARKER on Q→RQ\to R: closes that channel's recording; log is finalized as `[$20]`
t10t_{10}PP receives MARKER from RR: closes channel R→PR \to P (empty, nothing was sent on it)

Assembling the global snapshot: local states P=$60,Q=$60,R=$30P=\$60, Q=\$60, R=\$30, sum =$150=\$150; in-transit messages: channel Q→RQ\to R holds $20, all other channels empty. Grand total accounting for in-transit money: 150+20=170150 + 20 = 170? That looks wrong until we notice m2m_2's $20 was already debited from QQ's recorded $60 (i.e., QQ's $60 is after sending m1m_1 but the snapshot at QQ happened at t4t_4, before QQ sent m2m_2 at t6t_6). So QQ's recorded $60 does not yet reflect the $20 debit for m2m_2 — meaning m2m_2's $20 is money that, in the snapshot's consistent-cut view, has not left QQ and has not arrived at RR simultaneously; it is legitimately "in transit," counted once, and the total is:

60⏟P+60⏟Q+30⏟R+20⏟in transit Q→R−(nothing double counted)\underbrace{60}_{P} + \underbrace{60}_{Q} + \underbrace{30}_{R} + \underbrace{20}_{\text{in transit } Q\to R} - (\text{nothing double counted})

Wait — check conservation directly: originally 70+50+30=15070+50+30=150. m1m_1 ($10, P→QP\to Q) is fully reflected in both endpoints by snapshot time (PP debited to $60, QQ credited to $60 — net effect on the pair is a wash, total unchanged at 60+60=12060+60=120 vs. original 70+50=12070+50=120 ✓.) m2m_2 ($20, Q→RQ \to R) is only debited at QQ (already reflected: QQ went from a would-be $80 after receiving m1m_1, down to $60 after sending both — but wait, QQ's snapshot was taken at t4t_4, before m2m_2 was sent at t6t_6, so QQ's recorded $60 has not yet subtracted $20 for m2m_2). Let's recompute cleanly: QQ received $10 (now $60) then snapshot recorded — snapshot value $60. Later QQ sends $20 (now really $40 in QQ's actual post-snapshot state, but that's irrelevant to the snapshot). RR's snapshot recorded $30 before m2m_2 arrived. So the snapshot's version of the world is: P=$60P=\$60, Q=$60Q=\$60, R=$30R=\$30, plus $20 "on the wire." Total =60+60+30+20=170≠150= 60+60+30+20 = 170 \ne 150. The resolution: this reveals exactly why we must double check the trace — re-examine event t2t_2: PP's recorded state must reflect that PP already sent m1m_1 before recording (event order: t1t_1 send, t2t_2 record), so PP's recorded $60 is correct (started at $70, sent $10). QQ received m1m_1 at t3t_3, before recording at t4t_4, so QQ's recorded state correctly includes the $10 (now $60, from $50). So far P+Q=60+60=120P+Q = 60+60=120, matching pre-transfer 70+50=12070+50=120 — consistent, m1m_1 is "settled" and correctly not double-counted (it's not in-transit; it's been absorbed on both sides in causal order). Now for m2m_2: QQ sends it at t6t_6, which is after QQ's snapshot at t4t_4 — so this send is not part of QQ's recorded history at all. In a truly consistent cut, an event not yet recorded at the sender cannot have its effects appear anywhere in the snapshot, including as an "in-transit" message — because "in transit" means "sent-before-cut but not-yet-received-before-cut." Since m2m_2 was sent after QQ's cut, it should not appear in the channel-state log at all under a correct execution of the algorithm. This is exactly what the FIFO/marker mechanism prevents: at t8t_8, QQ sends the marker on Q→RQ\to R immediately upon recording, i.e., at t4t_4 (per Rule: markers go out right after recording, on every outgoing channel, not deferred to t8t_8). Correcting the trace: QQ's marker on Q→RQ\to R is actually sent at t4t_4, arriving at RR before t6t_6 (assuming reasonable delay), so by FIFO m2m_2 — sent at t6t_6, after the marker — arrives at RR after the marker, and Rule 2/3 dictates RR has already closed channel Q→RQ\to R's recording by the time m2m_2 shows up, so m2m_2 is delivered to RR's application layer normally but is correctly excluded from the snapshot. The corrected snapshot is P=60,Q=60,R=30P=60, Q=60, R=30, no in-transit messages, total =150=150 ✓ — consistent, because m2m_2 belongs entirely to the causal future of the cut, on both ends. This trace illustrates the single most important operational detail: markers must be sent as the immediate next action after recording, on every outgoing channel, precisely so that no application message a process sends after its own snapshot can be misfiled as "in transit" at a downstream process that hasn't yet closed that channel.

Common pitfalls & traps

PitfallWhy it breaks things
Assuming non-FIFO channels still workA post-snapshot application message can overtake the marker, get logged as "in transit," and violate consistency (as shown in the worked trace).
Delaying marker send after recordingIf a process does other work (including sending messages) before dispatching markers, those messages are wrongly excluded from or included in channel logs, breaking the causal invariant.
Forgetting to record local state on the first marker seen anywhereA process might otherwise record its state too late, after having already processed post-cut application messages, corrupting its local snapshot.
Treating channel state as always emptyBeginners often assume no messages are ever "in transit"; but any message sent before the sender's cut and still unreceived at the receiver's cut must appear, or conservation laws (like total money) will be silently violated.
Confusing "the snapshot" with "an instant in real time"The recorded global state may never have existed simultaneously on a wall clock; using it to infer real-time ordering of unrelated events is invalid — only causal (stable) properties are safely inferred.
Ignoring termination on non-strongly-connected graphsIf some process cannot be reached by a chain of markers, its state never gets recorded, and the algorithm never terminates; a spanning structure or extra "control" edges must be assumed.

Pioneers & history

The algorithm is named for two of the foundational figures in distributed computing theory. K. Mani Chandy, a systems and parallel-computing researcher, and Leslie Lamport, whose earlier work on logical clocks in "Time, Clocks, and the Ordering of Events in a Distributed System" (1978) supplied the causal machinery — the happened-before relation — on which the notion of a consistent cut depends. Their 1985 paper, "Distributed Snapshots: Determining Global States of Distributed Systems," gave the marker-based protocol described here and proved its consistency and termination properties rigorously, framing global-state detection as a first-class problem separate from, but foundational to, deadlock detection, checkpointing, and later consensus protocols.

Origins · 1 minLeslie B. Lamport and how this was discovered
LB
Leslie B. Lamport
born 1941 · Turing Award laureate, foundational contributor to distributed systems theory

Lamport formalized logical time and causal ordering, providing the theoretical bedrock — the happened-before relation and vector-clock-style reasoning — that makes precise what it even means for a distributed snapshot to be "consistent." His broader body of work, spanning Paxos consensus and the TLA+ specification language, treats distributed correctness as a mathematically provable property rather than an engineering hope.

History
- 1978 — Lamport publishes logical clocks and the happened-before relation, the causal foundation for consistent cuts.
- 1985 — Chandy and Lamport publish the marker-based distributed snapshot algorithm and its correctness proof.
- 1987 — Distributed snapshot ideas are extended to checkpoint-based rollback-recovery protocols in fault-tolerant systems research.
- 1990s — Snapshot and cut theory become standard tools for stable-property detection (deadlock, termination) in distributed debugging and monitoring systems.
- 2000s–present — Variants of consistent global-state capture underpin distributed stream-processing checkpointing (e.g., barrier-based checkpointing in modern data-processing engines) and distributed database consistency mechanisms.

Where this matters

Consistent global snapshots are a quiet workhorse underneath many production systems. Distributed stream-processing engines use a Chandy-Lamport-style barrier mechanism to checkpoint the state of long-running pipelines: a "barrier" record travels through the dataflow graph exactly like a marker, and each operator snapshots its state when barriers on all input streams have arrived, giving exactly-once processing guarantees after a failure and restart. Distributed deadlock detection algorithms build a global wait-for graph from a consistent snapshot, since checking for cycles on an inconsistent picture can produce phantom deadlocks or miss real ones. Distributed debugging and monitoring tools use consistent cuts to evaluate stable predicates — properties, like "no process holds a lock," that remain true once true — safely across an unsynchronized cluster. Database systems that need consistent, non-blocking backups of replicated or partitioned state rely on the same core insight, and the causal reasoning here directly foreshadows the coordination challenges tackled by Consensus Problem (agreeing on a single value despite failures) and by Distributed Transactions and Two-Phase Commit (agreeing on whether to commit a transaction spanning multiple machines) — both of which must, at bottom, reason about consistent global states under uncertainty.

Ask the tutor: How would the Chandy-Lamport algorithm need to change if channels could reorder messages instead of being FIFO?

Does every process need to pause execution during the snapshot?

No — this is one of the algorithm's key strengths. Processes continue running normally; application messages keep flowing and are delivered to the application layer as usual. The only extra work is recording a local state copy when a process first sees a marker, and logging (not blocking) certain incoming application messages into a channel-state buffer while that channel remains open. Because recording a local state snapshot can typically be done in O(1)O(1) time (e.g., copying a small set of variables or taking advantage of copy-on-write), the interruption to normal processing is negligible, making the algorithm suitable for live production systems rather than requiring a stop-the-world pause.

What happens if two processes initiate a snapshot at the same time?

The protocol handles concurrent initiators gracefully as long as each initiator follows the same rule: record local state, then immediately send markers on all outgoing channels. A process only performs the "first marker" recording action once — subsequent markers, from any source, are treated by the "already recorded" branch, simply closing that channel's logging. In practice systems often designate a single logical initiator or coordinate via a monitor to avoid duplicate/interleaved snapshots that could otherwise complicate collecting a single coherent result, but the algorithm's correctness does not strictly require a unique initiator.

Can this algorithm detect that the whole system has terminated?

Yes, and this is one of its classic applications: termination is a stable property (once true, it stays true), so it can be safely checked on any consistent snapshot. If a snapshot shows every process locally idle and every channel's recorded state empty, the system was in a globally quiescent state at that consistent cut, and since termination is stable, the system remains terminated afterward. This differs from unstable properties — e.g., "process X is currently in its critical section" — which can be true at the moment of the real cut yet already false by the time you observe the recorded result, so snapshots cannot safely detect transient conditions.

How does this relate to vector clocks?

Vector clocks, from Vector Clocks, give each event a vector timestamp such that comparing two vectors reveals whether the events are causally ordered or concurrent. A consistent cut can equivalently be defined as a set of local prefixes whose vector-clock frontiers are pairwise comparable in the right direction — no cut point can be "ahead" of another in a way that includes an effect without its cause. Chandy-Lamport achieves the same consistency guarantee operationally, using markers on FIFO channels, without ever computing or exchanging vector timestamps, which makes it cheaper in message overhead when FIFO channels are available.

What if the communication graph is not strongly connected?

The basic algorithm assumes every process can eventually be reached by a marker originating from any process that initiates a snapshot, which requires strong connectivity (or at least that markers can reach every process from the initiator, possibly via extra logical control channels). If some subgraph is unreachable from the initiator, those processes never record a state, and the snapshot is incomplete. Practical deployments often add a spanning overlay of control channels dedicated to markers, decoupled from the application's actual message topology, to guarantee reachability regardless of the application-level communication pattern.

Is the snapshot's total (e.g., total money in the bank) always exactly equal to the true total?

Yes, provided the invariant being measured is conserved by the message-passing protocol itself (e.g., a transfer message always represents money leaving one account and arriving at another, with no leaks). The snapshot's consistency guarantees no double-counting and no omission: every unit of value is either sitting in exactly one process's recorded local state or in transit on exactly one channel's recorded log, never both, never neither. This is precisely the content of the correctness proof — the recorded channel state for each edge equals sent-before-cut minus received-before-cut, which prevents both duplication and loss.

Why can't we just have every process report its state at 12:00:00.000 by a synchronized clock?

Even with tightly synchronized clocks, network delays and message propagation times are unpredictable, so "state at 12:00:00.000" on different machines does not guarantee causal consistency — a message sent at 11:59:59.999 might arrive at 12:00:00.001, appearing in neither snapshot, or a message received at 11:59:59.998 might have been sent by a process whose recorded state (at 12:00:00.000) already reflects a later send, creating an impossible causal loop. Chandy-Lamport sidesteps clock synchronization entirely, using only message ordering (FIFO) and causality (markers), which is strictly more robust to real-world network jitter and clock drift.

Before this

Where this leads