Home/Learn/Microservices/Eventual Consistency

Eventual Consistency

Intermediate
Data Management

Distributed systems that reject strict ACID guarantees accept that replicas converge to the same value given enough time; design UIs and workflows around this reality.

Overview

Eventual consistency is the consistency model most microservices systems operate under. It means that if no new updates are made to a data item, all replicas of that item will eventually converge to the same value — but in the short term, different nodes (or services) may see different values. This is the natural consequence of asynchronous communication between services: when Service A updates its database and publishes an event, Service B's read model is updated asynchronously — there is a window where Service A's data is updated but Service B's is not. Designing for eventual consistency requires acknowledging the consistency window in the UI/UX, using optimistic locking for concurrent updates, and building idempotent consumers that handle out-of-order events gracefully.

The Consistency Window

The consistency window is the time between when a write is committed in the source service and when all read models and downstream services have caught up. This window can be milliseconds (fast consumers) or minutes (slow consumers, retries, back-off). During this window, stale reads are possible: a user places an order, immediately refreshes the order list, and the new order is not yet visible because the read model has not been updated.

CAP theorem: distributed systems can guarantee at most two of: Consistency, Availability, Partition Tolerance. Systems that choose AP (Available + Partition-tolerant, e.g., Cassandra, DynamoDB) sacrifice strong consistency for availability. Systems that choose CP (Consistent + Partition-tolerant, e.g., HBase, Zookeeper) sacrifice availability for consistency. Most microservices systems are AP by design.

Java (comments) — Consistency Window
// Eventual consistency example:
//
// 1. User places order → Order Service writes to DB, publishes OrderPlaced event
//    Order Service DB: orderId=123, status=PLACED  ✓
//
// 2. User immediately queries "my orders" via Order Query Service
//    Query Service reads Elasticsearch projection — still processing the event
//    Query Result: orderId=123 NOT YET VISIBLE  ← stale read
//
// 3. ~50ms later: Order Query Service processes OrderPlaced event
//    Elasticsearch: orderId=123, status=PLACED  ✓
//    User's next refresh: orderId=123 IS visible
//
// The window: ~50ms to a few seconds depending on consumer speed and load

// Pattern 1: Optimistic UI — assume success, show immediately
// After POST /orders, add the new order to the UI state locally
// without waiting for the query endpoint to reflect it
orderList.add(newOrder);  // client-side state update — eventual consistency UX trick

Designing for Eventual Consistency

Practical strategies for living with eventual consistency:

**1. Communicate staleness**: show "Updated just now" / "Syncing…" indicators so users understand freshness.

**2. Return write result immediately**: after a command (POST /orders), return the created resource from the command side immediately — do not wait for the query projection to update.

**3. Idempotent consumers**: events can arrive out-of-order or duplicated; design projections to handle this gracefully.

**4. Version/timestamp conflict detection**: use Optimistic Locking (version fields or timestamps) to detect concurrent modifications before they cause data corruption.

**5. Saga with compensating transactions**: for multi-service workflows, plan for partial failures and compensate explicitly rather than expecting atomic cross-service consistency.

Java — Patterns for Eventual Consistency
// Pattern 2: Return immediate result from command side
@PostMapping("/orders")
public ResponseEntity<Order> placeOrder(@RequestBody OrderRequest req) {
    Order order = orderCommandHandler.handle(new PlaceOrderCommand(req));
    // Return the order directly from the write side — don't query the projection
    // The projection will catch up in the background
    return ResponseEntity.status(HttpStatus.CREATED).body(order);
    // Client gets the order immediately; the query endpoint will be consistent shortly
}

// Pattern 3: Idempotent event consumer handles out-of-order events
@EventListener
public void on(OrderStatusUpdatedEvent event) {
    Optional<OrderProjection> existing = projectionRepo.findById(event.getOrderId());
    if (existing.isPresent() && existing.get().getVersion() >= event.getVersion()) {
        log.debug("Skipping stale event version {} for order {}",
                  event.getVersion(), event.getOrderId());
        return;  // do not apply older event over newer state
    }
    // Apply event
    projectionRepo.save(OrderProjection.from(event));
}

// Pattern 4: Optimistic locking on write side
@Entity
public class Order {
    @Version
    private Long version;  // incremented on every UPDATE; stale write → OptimisticLockException
}

Saga and Compensation — Handling Distributed Failures

In a Saga, each step is a local transaction that publishes an event. If a step fails, all previous steps must be compensated. The consistency window during a Saga means intermediate states are visible: an order may be in "PAYMENT_PENDING" state for a few seconds while the payment service processes the event. Design your domain to make these intermediate states explicit and valid.

Java — Explicit Intermediate States
// Explicit intermediate states handle the consistency window gracefully
public enum OrderStatus {
    // Stable states
    PLACED,
    PAID,
    SHIPPED,
    DELIVERED,
    CANCELLED,

    // Intermediate states (visible during Saga execution)
    PAYMENT_PENDING,      // waiting for payment service to respond
    INVENTORY_RESERVING,  // waiting for inventory reservation
    SHIPMENT_SCHEDULING,  // waiting for shipping service
    CANCELLING            // compensation in progress
}

// UI shows appropriate messaging for each state:
// PAYMENT_PENDING → "Processing payment..." (spinner)
// PAID            → "Payment confirmed" (green check)
// CANCELLING      → "Cancellation in progress..."
// CANCELLED       → "Order cancelled" (with refund info)

// Never design a Saga without intermediate states — the consistency window
// guarantees these states will be visible to users

Key Points to Remember

  • 1Eventual consistency means replicas converge over time — there is always a window where different services see different data.
  • 2CAP theorem: AP systems (most microservices) trade strong consistency for availability and partition tolerance.
  • 3Return write results immediately from the command side; do not block on the query projection catching up.
  • 4Idempotent consumers must handle out-of-order and duplicate events gracefully — use version fields to detect stale events.
  • 5Design explicit intermediate states (PAYMENT_PENDING, CANCELLING) for Saga workflows — these states WILL be visible to users.
  • 6Optimistic locking (@Version in JPA) on the write side detects concurrent modifications before they corrupt data.

Interview Questions

Sign in to ask Aria
1

What is eventual consistency and how does it differ from strong consistency?

EasyAmazon
2

A user places an order and immediately queries their order list — the order is missing. Why?

MediumUber
3

How does the CAP theorem relate to eventual consistency in microservices?

MediumGoogle
4

What are intermediate states in a Saga and why must they be explicitly modelled?

HardNetflix
5

How do you handle out-of-order events in an eventually consistent read projection?

HardLinkedIn

Ask Aria about Eventual Consistency

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…