Explain eventual consistency in distributed systems.
Quick Answer
Eventual consistency means that, without new updates, all replicas/services will converge to the same value over time, tolerating temporary inconsistency. It's common in distributed systems where each service owns its data and updates propagate asynchronously via events. It trades immediate (strong) consistency for availability and scalability, so designs must handle stale reads and use patterns like idempotency and compensating actions.
Detailed Answer
Eventual Consistency means that if no new updates are made to a data item, eventually all accesses to that item will return the last updated value. Unlike strong consistency, there may be a temporary period where different nodes have different versions of the data.
Key Concepts:
- Data updates propagate asynchronously across services
- Temporary inconsistencies are acceptable
- System favors availability over immediate consistency (CAP theorem)
- Eventually, all replicas converge to the same state
Example Scenario:
// Order Service - Creates order immediately
[HttpPost]
public async Task CreateOrder(CreateOrderRequest request)
{
var order = new Order
{
CustomerId = request.CustomerId,
Status = OrderStatus.Pending,
Total = request.Total
};
await _orderRepository.AddAsync(order);
// Publish event - asynchronous
await _messageBus.PublishAsync(new OrderCreatedEvent
{
OrderId = order.Id,
CustomerId = order.CustomerId,
Items = request.Items
});
return Ok(order); // Returns immediately, other services update eventually
}
// Inventory Service - Updates stock eventually
public class OrderCreatedHandler : IEventHandler
{
public async Task Handle(OrderCreatedEvent @event)
{
// This happens eventually, not immediately
foreach (var item in @event.Items)
{
await _inventoryRepository.DecreaseStockAsync(item.ProductId, item.Quantity);
}
}
}
// Notification Service - Sends email eventually
public class OrderCreatedNotificationHandler : IEventHandler
{
public async Task Handle(OrderCreatedEvent @event)
{
// This also happens eventually
var customer = await _customerRepository.GetByIdAsync(@event.CustomerId);
await _emailService.SendOrderConfirmationAsync(customer.Email, @event.OrderId);
}
}
Handling Eventual Consistency:
// 1. Optimistic UI Updates
public class OrderViewModel
{
public int OrderId { get; set; }
public string Status { get; set; }
public bool IsSyncing { get; set; } // Shows data is still being synchronized
}
// 2. Compensating Actions
public class OrderCompensationService
{
public async Task CompensateFailedOrder(int orderId)
{
// If inventory update fails, compensate by canceling the order
await _orderRepository.UpdateStatusAsync(orderId, OrderStatus.Cancelled);
await _messageBus.PublishAsync(new OrderCancelledEvent { OrderId = orderId });
}
}
// 3. Read Your Own Writes
public class OrderService
{
private readonly IMemoryCache _cache;
public async Task GetOrderAsync(int orderId, int userId)
{
// Check cache first for recently created orders
if (_cache.TryGetValue($"order:{orderId}:{userId}", out Order cachedOrder))
{
return cachedOrder;
}
return await _orderRepository.GetByIdAsync(orderId);
}
public async Task CreateOrderAsync(CreateOrderRequest request)
{
var order = await _orderRepository.AddAsync(new Order { /* ... */ });
// Cache for immediate read
_cache.Set($"order:{order.Id}:{request.UserId}", order, TimeSpan.FromMinutes(5));
return order;
}
}
// 4. Version Vectors/Timestamps
public class EventEnvelope
{
public T Event { get; set; }
public long Timestamp { get; set; }
public string EventId { get; set; }
public int Version { get; set; }
}
public class EventProcessor
{
private long _lastProcessedTimestamp;
public async Task ProcessEvent(EventEnvelope envelope)
{
// Ignore out-of-order events
if (envelope.Timestamp <= _lastProcessedTimestamp)
{
return;
}
await HandleEvent(envelope.Event);
_lastProcessedTimestamp = envelope.Timestamp;
}
}