Recipe

Vector clocks primer

Vector clocks let a distributed system order events without a global wall clock. Every node carries a counter per peer, increments its own on each event, and ships the full vector with every message. Compare two vectors to learn whether one event happened-before the other, or whether they are concurrent.

01.Why ordering matters

Lamport timestamps give a total order but lose causality. Vector clocks recover the partial order you actually need to reason about replicas, retries, and split-brain. If V(a) < V(b), event a causally precedes b. If neither dominates, the events are concurrent and your merge logic owns the conflict.

02.The three update rules

  • On a local event at node i, increment V[i] by one.
  • On send, attach the current vector to the outgoing message.
  • On receive, merge element-wise with max, then increment V[i].

03.Reference implementation

A minimal TypeScript clock that any Meridian worker can embed. Persist the vector alongside the event log, never in shared memory.

type Vec = Record<string, number>;

export function tick(v: Vec, id: string): Vec {
  return { ...v, [id]: (v[id] ?? 0) + 1 };
}

export function merge(a: Vec, b: Vec, id: string): Vec {
  const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
  const out: Vec = {};
  for (const k of keys) out[k] = Math.max(a[k] ?? 0, b[k] ?? 0);
  return tick(out, id);
}

export function happensBefore(a: Vec, b: Vec): boolean {
  let strict = false;
  for (const k of new Set([...Object.keys(a), ...Object.keys(b)])) {
    const x = a[k] ?? 0, y = b[k] ?? 0;
    if (x > y) return false;
    if (x < y) strict = true;
  }
  return strict;
}