Intro

Consistency models define what value a read is allowed to return relative to writes in a distributed system. They matter because every replication, partition-tolerance, and caching decision is also a consistency decision. You think about consistency when selecting storage, designing leader/replica topology, and deciding whether stale reads are acceptable. In interviews, the important skill is mapping each business invariant to the weakest model that still preserves correctness.

Models

Strong or Linearizable

  • Guarantee: reads behave as if operations happen in one real-time order.
  • Read rule: if write W2 completes before read R1 starts, R1 cannot return pre-W2 data.
  • Mechanism: leader-plus-consensus or equivalent linearizable-read protocol (lease/commit-index aware reads).
  • Example: single-leader PostgreSQL for order state.
  • Cost: highest coordination overhead and latency; availability drops during partitions.

Sequential

  • Guarantee: all operations appear in one total order that preserves each client’s program order.
  • Difference from linearizable: order must be globally consistent, but not tied to wall-clock completion time.
  • Mechanism: global serialization logic without strict real-time constraints.
  • Example: shared preference changes where clients must agree on one operation order.
  • Example: ZooKeeper exposes linearizable writes and sequentially consistent reads, so designs often separate write-path correctness from read freshness.
  • Cost: still coordination-heavy, but weaker real-time freshness than linearizable.

Causal

  • Guarantee: causally related operations must be seen in order.
  • Concurrency rule: unrelated concurrent operations can appear in different orders.
  • Mechanism: dependency tracking (for example version vectors or causal metadata).
  • Example: chat replies should not appear before the original message.
  • Example: collaborative edits preserve dependency chains while merging concurrent edits.
  • Cost: lower latency than strict global ordering, but extra dependency metadata.

Eventual

  • Guarantee: if no new writes occur, replicas converge.
  • Mechanism: asynchronous replication plus conflict resolution policy.
  • Example: DNS propagation.
  • Example: Dynamo-style stores and lagging Redis replicas.
  • Cost: cheapest coordination model, but stale reads are expected.
  • Design impact: application code must tolerate temporary divergence.

Read-your-writes or Session

  • Guarantee: a client sees its own successful writes in its session.
  • Mechanism: session token, sticky routing, or dependency-aware replica selection.
  • Example: user edits profile and immediately refreshes profile page.
  • Use case: common middle ground for user-facing flows.
  • Cost: lower than global strong consistency, but requires session propagation discipline.

Tradeoffs

ModelGuaranteeExample Use CaseCost
Strong / LinearizableLatest write in real-time orderPayments, inventory decrement, distributed lock stateHighest latency, lower availability under partition
SequentialOne global order preserving per-client orderShared config updatesHigh coordination, weaker real-time guarantee
CausalPreserve cause/effect orderChat threads, collaborative docsMedium complexity, dependency metadata
SessionClient sees own writesProfile/settings updatesLow-medium cost, session token plumbing
EventualConverges after writes stopCatalog/cache/reference dataLowest coordination, stale reads expected

Tunable consistency (Cosmos DB)

Azure Cosmos DB sets a default consistency level at the account level. For the API for NoSQL SDKs, clients and requests can relax reads to weaker levels, but cannot strengthen beyond the account default.

  • Strong: linearizable; not supported with multiple write regions; highest latency profile.
  • Bounded Staleness: lag bounded by versions or time window.
  • Session: read-your-writes per session.
  • Consistent Prefix: never out-of-order, but may miss latest writes.
  • Eventual: weakest ordering, best throughput/latency.

This is a practical tradeoff spectrum: moving from Strong toward Eventual reduces coordination cost and latency while increasing stale-read risk. During a partition, preserving stronger consistency typically means reduced availability for affected requests, which is the operational lens of CAP theorem.

Pitfalls

  • Assuming strong when behavior is eventual: stale reads cause duplicate actions, confusing UI, and contradictory confirmations.

    • Cause: implicit read-after-write assumptions in asynchronously replicated paths.
    • Mitigation: explicit freshness contracts, session consistency on critical UX flows, idempotent writes, and version checks.
  • Over-specifying consistency: paying strong-consistency latency for low-value reads.

    • Cause: one-size-fits-all policy across all entities.
    • Mitigation: classify data by invariant criticality; reserve strongest guarantees for correctness-critical aggregates.
  • Mixing levels without boundary rules: one service assumes fresh reads while another serves lagged projections.

    • Cause: architecture docs list storage systems but not consistency contracts.
    • Mitigation: define consistency in API contracts and test with induced replica lag.

Practical decision checklist

  • Define the invariant first: what must never be wrong from a business perspective.
  • Separate write-path correctness from read-path freshness; they are often different requirements.
  • Decide whether read-after-write must hold for everyone or only for the acting user.
  • For each endpoint, set an explicit freshness budget (for example: “up to 2 seconds stale”).
  • Model partition behavior up front: which operations fail closed vs continue degraded.
  • Add observability for replica lag, cache age, and stale-read rate.
  • Use Idempotency keys on writes so retries are safe when consistency is weaker.
  • Test failure modes with delayed replication and partial-region outages.
  • Keep consistency decisions visible in architecture docs and API contracts.

Example: Optimistic Concurrency for Read-Your-Writes

ETag-based optimistic concurrency enforces that a write only succeeds if the client’s version matches the server’s current version — a practical implementation of session consistency for HTTP APIs:

// GET /orders/42 returns ETag: "v3"
// Client stores the ETag and sends it on the next write
 
// PUT /orders/42 with If-Match: "v3"
// If another writer committed between the GET and PUT, the server returns 412 Precondition Failed
app.MapPut("/orders/{id}", async (int id, OrderUpdate update,
    HttpContext ctx, OrderRepository repo) =>
{
    var etag = ctx.Request.Headers.IfMatch.FirstOrDefault();
    var order = await repo.GetAsync(id);
    if (order is null) return Results.NotFound();
 
    // Reject if the client's version is stale (another writer committed first)
    if (etag is not null && etag != order.ETag)
        return Results.StatusCode(412);  // Precondition Failed
 
    order.Apply(update);
    order.ETag = Guid.NewGuid().ToString("N");
    await repo.SaveAsync(order);
    return Results.Ok(order);
});

This pattern implements read-your-writes at the API layer: the client reads the current ETag, includes it on the write, and the server rejects stale writes. The client retries with a fresh GET if it receives 412.

Questions

References