Low Level Design: Vector Clock Service

Vector Clock Service

Vector clocks track causality in distributed systems without requiring synchronized physical clocks. Each node maintains a vector of logical timestamps — one counter per node in the system.

Vector Clock Mechanics

VC = [VC[0], VC[1], ..., VC[n-1]]
// VC[i] = number of events node i has caused

Rules

  • Increment: Before a send event or internal event, node i increments VC[i].
  • Send: Attach the current VC to the message.
  • Receive / Merge: For each component j, set VC[j] = max(local_VC[j], received_VC[j]), then increment own counter VC[i]++.

Causality Detection

  • Happened-before (A → B): VC(A) <= VC(B) — all components of A are less than or equal to the corresponding components of B, with at least one strictly less.
  • Concurrent (A ∥ B): neither VC(A) <= VC(B) nor VC(B) <= VC(A).
  • Identical: same event or causally equivalent.

Data Model

VersionedObject {
    key:          string       PRIMARY KEY
    value:        bytes
    vector_clock: JSONB        // {"node_id": counter, ...}
    updated_by:   string       // node_id of last writer
    updated_at:   timestamp
}

Conflict Detection on Write

  1. Client sends write with its current VC.
  2. Server reads stored VC_stored.
  3. Compare: if VC_client >= VC_stored (client has seen everything stored), write is safe — update and merge clocks.
  4. If concurrent (neither dominates), a conflict exists: store both versions as sibling values.

Conflict Resolution Strategies

  • Last-Write-Wins (LWW): use wall clock timestamp as tiebreaker. Simple but loses updates.
  • Application-level merge: e.g., shopping cart union — take union of all sibling item sets.
  • CRDT: use a data structure that merges automatically (see LLD: CRDT).
  • Client-side reconciliation: return all siblings to client; client resolves and writes back.

Dotted Version Vectors

Standard VCs can't distinguish between concurrent writes and causally-related ones when siblings are present. Dotted Version Vectors (DVVs) attach a "dot" (node, counter) to each value, enabling precise tracking of which write introduced each sibling.

DVV = { dot: (node, counter), context: VC }

Practical Implementation: Amazon Dynamo-style KV Store

  • Each replica maintains its own VC component.
  • On read, coordinator gathers responses from R replicas; returns all siblings if VCs are concurrent.
  • On write, coordinator increments its own VC component and writes to W replicas.
  • Background anti-entropy: replicas gossip and merge state using VC comparison to detect and repair divergence.

Key Design Decisions

  • VC size: grows with number of nodes. Prune stale entries using a garbage collection epoch when nodes leave.
  • Storage: store VC as JSONB for flexibility; index on key for fast reads.
  • Clock skew: VCs are immune to clock skew — they use logical, not physical, time.
  • Read repair: on read, if coordinator detects concurrent siblings, trigger a reconciliation write.

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Databricks Interview Guide 2026: Spark Internals, Delta Lake, and Lakehouse Architecture

Scroll to Top