Skip to main content

Time & Clock Synchronization

Time is the foundation upon which all distributed systems reasoning is built. Understanding clocks is crucial for designing systems that maintain consistency and order. Here is the uncomfortable truth that makes this chapter important: there is no “now” in a distributed system. On a single computer, “now” is a meaningful concept — you can read the clock and trust it. In a distributed system, each machine has its own clock, each clock drifts differently, and the act of asking another machine “what time is it?” takes a non-zero amount of time. This means that every timestamp-based decision (who wrote first? has this lease expired? is this cache entry stale?) is fundamentally uncertain. The rest of this chapter is about different strategies for coping with that uncertainty.
Track Duration: 10-14 hours
Key Topics: Physical Clocks, NTP, PTP, Logical Clocks, Vector Clocks, HLC, TrueTime
Interview Focus: Google Spanner’s TrueTime, causality tracking, clock synchronization trade-offs

The Fundamental Problem

The Hard Truth: There is no global clock in distributed systems. Every node has its own view of time, and they never perfectly agree.

Module 33: Clock Synchronization Protocols

Physical Clock Synchronization

Network Time Protocol (NTP)

NTP is the most widely used clock synchronization protocol on the internet.

NTP Synchronization Algorithm

Precision Time Protocol (PTP - IEEE 1588)

For applications requiring sub-microsecond accuracy:

Clock Anomalies and Edge Cases

Real-World Clock Problems

Earth’s rotation is gradually slowing, requiring occasional corrections.
Interview Tip: Explain how your timestamp-dependent features handle leap seconds.
When NTP decides the clock is too far off, it jumps:
For small corrections, NTP gradually adjusts the clock rate:
Virtual machines have additional clock challenges:

Module 34: Logical and Vector Clocks

Logical Clocks Deep Dive

Lamport Timestamps

Implementation:

Vector Clocks

Vector clocks capture causality - they can tell you if two events are concurrent:
Implementation with Conflict Detection:

Vector Clock in DynamoDB/Riak


Module 35: Hybrid Logical Clocks (HLC)

Used by CockroachDB, MongoDB, and many modern databases:
Implementation:

Module 36: TrueTime and Atomic Clocks

Google TrueTime

The gold standard for distributed time synchronization:

Spanner’s External Consistency


Fault-Tolerant Clock Synchronization

In large-scale systems, some nodes might have faulty clocks or even malicious intent (Byzantine failures).

Marzullo’s Algorithm

Marzullo’s algorithm is used to select a confidence interval from a set of noisy time sources.

The Uncertainty Window & Intersection

In high-accuracy systems (like PTP or TrueTime), the clock is never a point, but an interval: I=[tϵ,t+ϵ]I = [t - \epsilon, t + \epsilon].

The Overlap Rule

For two events e1e_1 and e2e_2 to be definitively ordered (e1<e2e_1 < e_2), their uncertainty intervals must not overlap: e1.high<e2.lowe_{1}.high < e_{2}.low If they overlap, the system cannot determine the true order based on physical time alone. This is the Causality Gap.

Multi-Source Intersection (The “Master” Interval)

When a node queries NN time sources, it uses Marzullo’s (or the Improved Marzullo/Intersection algorithm) to find the “True” interval. If the sources disagree significantly, the interval grows (increasing uncertainty) or the node enters a “Panic” state. Staff Tip: When designing systems that rely on time for consistency (like Spanner), you must explicitly handle the “Overlap Case” by either:
  1. Waiting: Wait until the uncertainty window of e1e_1 has passed before starting e2e_2 (Commit-Wait).
  2. Versioning: Use a logical counter (HLC) to break ties during the overlap.

Byzantine Clock Synchronization

If ff nodes are Byzantine (malicious), we need at least 3f+13f+1 total nodes to synchronize clocks correctly.
  • Lynch-Welch Algorithm: Nodes exchange their clock values. Each node discards the ff highest and ff lowest values and takes the average of the remaining n2fn-2f values.
  • Clock Drift Bounds: In a Byzantine environment, the maximum skew between correct clocks is bounded by Δϵ+ρR\Delta \approx \epsilon + \rho R, where ϵ\epsilon is message delay uncertainty, ρ\rho is drift rate, and RR is synchronization interval.

Practical Guidelines

Choosing the Right Clock

  • Generating user-facing timestamps
  • Debugging and logging
  • TTL (time-to-live) calculations
  • Scheduling future events
  • Audit trails and compliance
Caveat: Accept that ordering might be wrong across machines

Clock Best Practices


Interview Questions

Answer:
  1. TrueTime API: Returns time interval [earliest, latest] instead of single value
  2. GPS + Atomic Clocks: Hardware in every datacenter for accurate time
  3. Commit Wait: After getting timestamp, wait until uncertainty period passes
  4. Guarantee: If T1 commits before T2 starts, T1’s timestamp < T2’s timestamp
The wait adds latency (1-7ms) but provides global ordering without coordination.
Answer:
  1. Clock Skew: Different machines have different times (milliseconds to seconds)
  2. Clock Drift: Clocks run at slightly different speeds
  3. NTP Jumps: Clocks can jump forward or backward during sync
  4. No Causality: Physical time doesn’t capture happened-before relationships
Example: Machine A (behind) writes at “10:00:00.100”, Machine B (ahead) writes at “10:00:00.050” - B’s write appears earlier despite happening after A’s!
Answer:Vector Clocks:
  • When you need precise conflict detection
  • Systems like DynamoDB/Riak that return conflicting versions
  • When number of nodes is bounded and small
HLC:
  • When you need approximate physical time for debugging
  • Systems with many nodes (vector clock size = O(n))
  • Databases like CockroachDB, MongoDB
  • When snapshot isolation is needed
Key difference: Vector clocks track all participants, HLC bounds clock size.
Answer approach:
  1. Understand requirements: Strong ordering (expensive) vs causal ordering (cheaper)
  2. For causal ordering:
    • Use HLC at each data center
    • Propagate timestamps with messages
    • Compare HLC timestamps for ordering
  3. For strong ordering:
    • Central sequencer (single point of failure)
    • OR distributed consensus (high latency)
    • OR TrueTime-like approach (hardware investment)
  4. Practical trade-off:
    • Use causal ordering where possible
    • Strong ordering only where required (e.g., financial transactions)

Key Takeaways

Perfect Time is Impossible

Accept uncertainty. Design systems that handle clock skew gracefully.

Causality ≠ Physical Time

Use logical or hybrid clocks when you need to reason about event ordering.

Measure and Monitor

Track clock drift between nodes. Alert when skew exceeds thresholds.

Choose the Right Tool

Physical for display, logical for causality, HLC for both. TrueTime if you’re Google.

Interview Deep-Dive

Strong Answer:
  • HLC gives you causality tracking (if event A causes event B, HLC guarantees A’s timestamp is less than B’s) plus a close approximation of physical time, all in software with zero hardware investment. CockroachDB and MongoDB both use HLC. The downside is that HLC cannot provide external consistency — if two unrelated transactions happen in different regions, HLC cannot guarantee their timestamps reflect real-world ordering because the physical clocks they are based on have unbounded skew (in theory).
  • TrueTime gives you bounded uncertainty intervals — the system knows the actual time is within a window (typically 1-7ms). This allows Spanner’s commit-wait protocol: after assigning a timestamp, the transaction waits until the uncertainty interval has passed, guaranteeing that no future transaction can receive a lower timestamp. This achieves external consistency. The cost is GPS receivers and atomic clocks in every data center, plus the latency overhead of the commit-wait (equal to the uncertainty interval).
  • For most companies, HLC is the right choice. External consistency matters only when you need globally ordered transactions across independent shards with no causal relationship. If your workload can tolerate “causal consistency” rather than “strict serializable across unrelated transactions,” HLC is sufficient and dramatically simpler to operate.
  • The CockroachDB compromise is instructive: they use HLC but also enforce a maximum clock skew bound. If a node’s clock drifts beyond that bound, it self-quarantines. This provides “external consistency within the skew bound” without specialized hardware.
Follow-up: What happens in CockroachDB if two nodes have clocks skewed by more than the configured maximum offset?CockroachDB’s safety depends on a clock skew bound (default 500ms). If a node’s clock exceeds this offset, the node will refuse to serve reads and writes to prevent consistency violations. But the real danger is if the skew exceeds the bound without being detected. In that case, a transaction T1 on node A could commit with timestamp 100, and a later transaction T2 on node B (whose clock is far behind) could commit with timestamp 90, violating external consistency. CockroachDB mitigates this with “uncertainty intervals”: when a transaction reads data, it treats any version within the uncertainty window as potentially concurrent and restarts the transaction with a higher timestamp. This means clock skew does not cause correctness bugs — it causes transaction restarts and higher latency. The system degrades gracefully rather than silently corrupting data.
Strong Answer:
  • Lamport timestamps assign a single integer counter to each event. The rule is: before any event, increment the counter; when sending a message, attach the counter; when receiving, set your counter to max(local, received) + 1. This guarantees that if event A happened-before event B (causally), then L(A) is less than L(B).
  • The fundamental limitation is the converse is not true: L(A) less than L(B) does NOT imply A happened before B. Two completely independent events on different nodes can have ordered timestamps by coincidence. You cannot distinguish “A caused B” from “A and B were concurrent but A happened to get a lower number.” This means Lamport timestamps cannot detect conflicts.
  • Vector clocks fix this by maintaining one counter per node. Each node increments only its own entry. When sending, it attaches the entire vector. When receiving, it takes the element-wise max and increments its own entry. Now you can compare two vectors: if every entry in V1 is less than or equal to V2, and at least one is strictly less, then V1 happened before V2. If neither dominates the other (V1 has some entries greater, V2 has others greater), the events are concurrent — a true conflict.
  • The trade-off is size: vector clocks grow linearly with the number of nodes. For a system with thousands of nodes, this overhead is prohibitive. This is why systems like DynamoDB originally used vector clocks but later moved to simpler mechanisms (last-writer-wins with server-side timestamps).
Follow-up: If vector clocks are O(n) in the number of nodes, how do real systems handle this in clusters with thousands of nodes?There are several practical approaches. First, version vectors instead of vector clocks: version vectors only track replicas that modify data (often 3-5 in a typical replication group), not all nodes in the cluster. This keeps the vector small. Second, dotted version vectors (used in Riak) which are more space-efficient and can accurately prune entries from nodes that are no longer relevant. Third, many systems abandon vector clocks entirely and use HLC or simple last-writer-wins with wall-clock timestamps, accepting the possibility of lost updates in exchange for O(1) metadata. The right choice depends on whether conflict detection is critical for your use case. For a shopping cart (Amazon Dynamo), detecting conflicts matters — you do not want to silently drop items. For a metrics counter, last-writer-wins is fine because counters can be re-aggregated.
Strong Answer:
  • When a Spanner transaction is ready to commit, it acquires locks on all participants, then gets a commit timestamp s = TT.now().latest — the upper bound of the current TrueTime uncertainty interval.
  • Then it waits. Specifically, it waits until TT.after(s) returns true, meaning the system is now certain that time s has definitively passed on every node in the world. This wait is typically 1-7ms, equal to twice the TrueTime uncertainty epsilon.
  • After the wait, the transaction’s effects are made visible and locks are released.
  • Why this guarantees external consistency: suppose transaction T1 commits with timestamp s1 and then a client, having observed T1’s commit, starts transaction T2. T2 starts at real-time t2, which is after T1’s commit-wait completed. Therefore t2 > s1 in absolute real time. When T2 calls TT.now(), the returned interval will have earliest >= t2 - epsilon > s1 (because we waited until s1 was definitely in the past). So T2’s commit timestamp s2 = TT.now().latest >= t2 > s1. This guarantees s2 > s1, meaning T2 is ordered after T1 in the commit order.
  • The brilliance is that this works without any coordination between T1 and T2 — they could be on different continents, different Paxos groups. The global ordering comes from physics (bounded clock uncertainty) rather than communication.
Follow-up: What would happen if the TrueTime uncertainty interval suddenly grew much larger — say from 7ms to 500ms?The commit-wait latency would increase proportionally. Every transaction would now wait up to 500ms before its commit is visible, making the system effectively unusable for interactive workloads. This is why Google invests heavily in reducing epsilon: they deploy GPS receivers and atomic clocks in every data center, and the TrueTime daemon continuously calibrates against multiple time sources. If a GPS antenna fails or a time master becomes unreliable, epsilon grows for nodes that depend on it. Google monitors epsilon closely — it is a key operational metric. In the absolute worst case, if epsilon grows unbounded (all time sources fail), Spanner would have to choose between blocking indefinitely (maintaining external consistency but losing availability) or proceeding without commit-wait (gaining availability but risking consistency). The system chooses to block, because Spanner’s entire value proposition is consistency. This is a concrete example of the CAP trade-off: during a “time partition” (inability to bound clock uncertainty), Spanner sacrifices availability.