Eventual Consistency
IntermediateDistributed 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.
// 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 trickDesigning 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.
// 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.
// 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 usersKey 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 AriaWhat is eventual consistency and how does it differ from strong consistency?
A user places an order and immediately queries their order list — the order is missing. Why?
How does the CAP theorem relate to eventual consistency in microservices?
What are intermediate states in a Saga and why must they be explicitly modelled?
How do you handle out-of-order events in an eventually consistent read projection?
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.