Vector Clocks — Distributed Causality
debt(d9/e7/b7/t7)
Closest to 'silent in production until users hit it' (d9). No detection tools are listed in detection_hints. Misuse of vector clocks — such as using Lamport timestamps instead, dropping the clock from messages, or missing concurrency detection — produces no compile-time, lint-time, or static-analysis errors. The failure mode is data inconsistency or missed conflict detection that only surfaces when concurrent writes cause silent data corruption or incorrect merge decisions under real distributed load.
Closest to 'cross-cutting refactor across the codebase' (e7). The quick_fix acknowledges that manual vector clock implementation is inadvisable and recommends switching to a database or library that handles causality (Riak, Cassandra). Retrofitting causal consistency into a distributed system that was built without it — fixing message routing to carry clocks, updating all nodes to increment and merge correctly, handling unbounded growth — touches every service boundary and data model, making this a cross-cutting architectural change rather than a localised fix.
Closest to 'strong gravitational pull' (e7). Once a distributed system adopts a causal consistency model via vector clocks, every component that sends or receives messages must participate: all nodes must carry and merge the clock, all conflict-resolution paths must interpret clock comparisons, and any new service added to the system must conform to the causal protocol. The choice shapes how every future feature involving distributed state is designed.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The canonical misconception is explicit: developers familiar with Lamport timestamps assume they provide concurrency detection because they are also called 'logical clocks.' In fact, Lamport timestamps impose a total order but cannot distinguish concurrent events. A competent developer who has used Lamport timestamps will confidently reach for them when concurrency detection is required and will be wrong — a direct contradiction between two superficially similar concepts in the same domain.
Also Known As
TL;DR
Explanation
Physical clocks on different machines drift and cannot reliably order events. A vector clock assigns each node an integer counter. When a node processes an event it increments its own counter. When it sends a message, it attaches its current vector. On receiving a message, the recipient merges the vectors by taking the maximum of each position, then increments its own. Given two vector clocks VC(A) and VC(B): if every position of VC(A) is ≤ VC(B) and at least one is strictly less, A causally precedes B. If neither dominates the other, the events are concurrent — no causal relationship exists. Amazon Dynamo used vector clocks for conflict detection. DynamoDB, Riak, and many distributed databases use vector clock variants.
Common Misconception
Why It Matters
Common Mistakes
- Using Lamport timestamps when you need concurrency detection — scalar Lamport clocks provide ordering but cannot tell you two events were concurrent.
- Not including the vector clock when sending messages — causality is only tracked if the clock travels with the data.
- Unbounded vector clock growth — in systems with many nodes, vectors grow indefinitely; use version vectors or bounded variants for production.
- Conflating 'concurrent' with 'conflicting' — concurrent events are not necessarily in conflict; application logic determines whether a conflict needs resolution.
Code Examples
// ❌ Using wall-clock timestamps to order distributed events
$event = [
'data' => $payload,
'timestamp' => microtime(true), // Different servers have different clocks
];
// Events from two nodes with 50ms clock skew cannot be ordered correctly
// 'Last write wins' based on this timestamp loses causally later updates
<?php
// ✅ Vector clock implementation
class VectorClock
{
private array $clock = []; // nodeId => counter
public function increment(string $nodeId): void
{
$this->clock[$nodeId] = ($this->clock[$nodeId] ?? 0) + 1;
}
public function merge(VectorClock $other): VectorClock
{
$merged = clone $this;
foreach ($other->clock as $nodeId => $count) {
$merged->clock[$nodeId] = max($merged->clock[$nodeId] ?? 0, $count);
}
return $merged;
}
public function isConcurrentWith(VectorClock $other): bool
{
$aLessB = false;
$bLessA = false;
$allNodes = array_unique(array_merge(array_keys($this->clock), array_keys($other->clock)));
foreach ($allNodes as $node) {
$a = $this->clock[$node] ?? 0;
$b = $other->clock[$node] ?? 0;
if ($a < $b) $aLessB = true;
if ($b < $a) $bLessA = true;
}
return $aLessB && $bLessA; // Neither dominates → concurrent
}
}