Explain eventual consistency in distributed systems.

4 minadvancedmicroservicesdistributed-systemsconsistency

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;
    }
}