skip to content

In a causally consistent data store, how does a write's set of causal dependencies get determined, and what rule must the store enforce before it lets a replica expose that write to readers?

level: middleimportance: must knowfreq 55%

answer

  1. causal context = read set + prior writes, transitively
  2. nearest-dependency trick bounds metadata
  3. causal cut: dependency before visibility
  4. COPS explicit dependency lists
  5. garbage collection of dependency metadata is hard

basics

~20 s

A write's dependencies are every earlier write the client has read or written before making this write. The store must make sure a replica shows all of those earlier writes first, before it shows the new one — otherwise readers could see an effect without its cause.

solid answer

~40 s

Every write carries an implicit causal context: the writes the issuing client has previously read, plus its own prior writes in program order, transitively closed over what those depended on. A store tracks this context per write (in COPS, an explicit list of the write's identifying dependencies; more generally, some form of dependency metadata attached to each update). Before a replica applies an incoming write and makes it visible to local readers, it must first check that every dependency in that write's context is already present and visible on that replica. If a dependency is missing, the replica must buffer or delay the incoming write until the dependency arrives — satisfying the 'causal cut.' Enforcing this for every write is what guarantees happens-before order is preserved system-wide without a single global sequencer.

go deeper

for a junior

Should be able to say, in plain terms, that a write 'remembers' what it was based on, and that the system waits to show a write until what it depends on is already visible.

for a middle

Should describe the causal context (reads + prior writes, transitively) and the causal-cut rule (dependency before visibility) with a concrete failure example like a delayed reply.

for a senior

Should know the nearest-dependency optimization and be able to discuss the metadata/garbage-collection cost trade-off, plus name a real system (COPS) that implements this.

for a principal

Should be able to reason about dependency explosion on hot keys, design a mitigation (e.g., session-scoped coarser tracking, TTL-based GC of dependency state), and weigh it against alternative architectures at a system-design level.

## How a write's dependencies are determined A write's causal dependencies are determined mechanically from the causal history of the client that issued it, not by any global mechanism. Concretely, when a client issues write W, its **causal context** is the union of: - everything the client previously read — reading a value creates a **read-from** dependency on whichever write produced it; - plus everything the same client previously wrote in program order; - plus, transitively, everything those earlier writes themselves depended on. In practice a system doesn't need to attach the full transitive closure to every write. A well-known optimization, used by **COPS**, is to track only the **nearest** dependencies (the writes a client directly read or issued most recently for each key), because each of those writes already carries its own dependency list, so satisfying the nearest dependency transitively satisfies everything further back in the chain. This keeps per-write metadata bounded by roughly the number of distinct keys a client's session has touched recently rather than growing with the entire system history. ## Why the check is needed at all This mechanism exists because replication in a causally consistent store is asynchronous and multi-path: writes propagate between replicas over independent network links (or via gossip), and nothing about the network guarantees they arrive in the order they were issued. Without an explicit dependency check, a replica could apply a 'reply' write before the 'original post' write it depends on simply because it arrived over a faster link. The rule that fixes this — satisfying the **causal cut** — says: before a replica makes an incoming write visible to any local reader, it must first confirm every write named in that write's dependency set is already present and visible locally. If any dependency is missing, the replica cannot expose the write yet — it must hold it in a pending buffer and re-check as further writes arrive, applying it only once its dependencies are locally satisfied. ## The trade-off The trade-off here is metadata and latency against ordering correctness. But the cost is real on each approach: | Approach | Why it appeals | What it costs | |---|---|---| | Explicit per-write dependency lists, as in **COPS** | attractive because they're precise, and let a replica check locally, without consulting a remote coordinator, whether it's safe to expose a write — exactly what keeps causal consistency compatible with high availability under partitions | every write now carries extra bytes of dependency metadata, and that metadata has to be garbage-collected once dependencies are known to be satisfied everywhere, itself a nontrivial distributed problem | | Coarser alternatives exist — e.g., treating an entire client session's causal history as a single opaque token rather than fine-grained per-key dependencies | in exchange, simpler and cheaper tracking | reducing bookkeeping precision, and sometimes causing a write to wait on dependencies it doesn't really need | ## The production failure mode The most visible production failure mode is **dependency stalling**: a write sits in a replica's pending buffer, invisible to readers, because one of its dependencies hasn't arrived — perhaps the source replica is slow, a link is congested, or a partition has cut off the path the dependency needed. This shows up as uneven, hard-to-predict write visibility lag: two writes issued moments apart by the same client can become visible on a remote replica at very different times depending on how quickly each one's dependencies propagate. In systems with **hot** keys many clients depend on (a popular post, a frequently-read config value), dependency chains can also grow large enough that tracking and checking them becomes a measurable per-write cost — sometimes called **dependency explosion**. ## Where it shows up A concrete real-world illustration is the **COPS** system (Clusters of Order-Preserving Servers), from Lloyd et al.'s 2011 paper, explicitly built to demonstrate that this per-write, nearest-dependency tracking approach could deliver causal consistency across geo-replicated data centers while keeping metadata overhead and latency low, in contrast to naively tracking the full transitive history of every write.

  • Why is tracking only the 'nearest' dependencies enough, instead of the full transitive history of every write a client has ever seen?
    Because each earlier write already carries its own dependency list and the receiving replica already had to satisfy that list before exposing it. So once a replica has verified the nearest dependency is visible, transitivity guarantees everything further back the chain is already visible too — re-listing the whole history on every write would be redundant and would make metadata grow without bound.
  • What happens to a replica that can never receive one of a write's dependencies, for example because the source data center is permanently lost?
    The write is stuck in the pending buffer indefinitely unless the system has a separate policy for it — some systems set a timeout and either surface an error or route around the lost dependency once the source is confirmed gone and its data reconstructed elsewhere. This is a genuine operational risk of dependency-based systems, usually handled by the broader durability/failure-recovery layer, not the causal-consistency protocol itself.

It's like a courier who won't hand you a package marked 'assemble step 2' until you've already received the box marked 'step 1' — the courier holds step 2 in the back room and keeps checking, rather than handing it over out of order.

saying these in an interview costs you the question

  • Says a write's dependencies must include every write ever made in the system rather than the causal context of the issuing client.
  • Thinks a replica can expose a write immediately upon receipt regardless of whether dependencies have arrived.
  • Cannot explain why unbounded dependency metadata is a real operational problem.
  • Confuses 'dependency tracking' with a specific logical-clock algorithm rather than describing the general read-set/write-set mechanism.

context