Phase 2: APIs & Databases

Eventual vs strong consistency in distributed stores

Intermediate ~3 min read
Think of it this way A friendly analogy. Read this if the technical version feels dense. Show Hide

Imagine a huge library with many different branches spread across a city, all sharing their books. Keeping track of every single copy, like if it’s on the shelf or checked out, gets tricky because information has to travel between all those places. What if someone returns a popular book at one branch, but someone at another branch asks for it just a second later? How quickly does everyone know the very latest news about that book? This is where computer systems, like our library, have to decide how "consistent" their information needs to be.

One way is like a super strict library. When someone returns "The Amazing Adventures of Sparky the Dog" at the downtown branch, every single other branch has to immediately update their system and confirm they know it's back. Only then can anyone check if Sparky is available anywhere. If just one branch's computer is slow or disconnected, then for a short time, no one can even ask about Sparky. You're always guaranteed the most accurate, perfect answer with this method, but it can sometimes make things slower because everyone must wait for all branches to catch up.

Another way is more relaxed. When "Sparky" is returned at the downtown branch, that branch immediately updates its own system and tells the person it's back. The other branches will get the update, but it might take a few seconds for the information to spread. So, someone at the uptown branch who asks for "Sparky" right after it was returned might briefly be told it's still unavailable. You know the information will eventually be correct everywhere, but for a short moment, different branches might briefly have slightly different info. This way is usually much faster because you don't have to wait for everyone to be perfectly in sync all the time.

So, when you're building something that needs to store information, like a game where scores must be perfectly correct for every player, you might choose the "super strict library" way. But if you're building something like a social media app where it’s fine if a friend's new post appears a few seconds later on different screens, then the "relaxed library" way would be faster and perfectly fine. This means you can decide whether speed or absolute, instant accuracy is more important for what you're creating.

When working with distributed databases like NoSQL stores (MongoDB, Redis, DynamoDB), a crucial concept is consistency: how and when changes made to the data become visible to other readers. In a distributed system, data is often replicated across multiple servers for fault tolerance and performance. This replication introduces challenges, as network latency means not all copies can be updated simultaneously. This is where eventual vs. strong consistency comes into play, dictating the trade-offs your application will make between data freshness, availability, and performance.

Strong consistency ensures that once a write operation is confirmed, all subsequent read operations will see the most recent, updated data. It's like everyone instantly sees the same, latest version of a document. This is often achieved by ensuring all replicas are updated and acknowledged before a write is considered complete, or by directing reads to the primary replica. While this provides an intuitive, easy-to-reason-about data model, it can introduce higher latency for writes and reads, and may sacrifice availability during network partitions, as the system might block reads or writes until consistency can be guaranteed across all replicas. Traditional relational databases often provide strong consistency by default, and NoSQL databases like MongoDB offer strong consistency for reads from the primary, or DynamoDB offers an explicit "strongly consistent read" option.

Eventual consistency, on the other hand, prioritizes availability and performance. With eventual consistency, a write operation is confirmed quickly, even if some replicas haven't yet received the update. This means that a subsequent read operation might temporarily see an older version of the data. However, the system guarantees that eventually, all replicas will synchronize, and all reads will reflect the latest write, assuming no further writes occur. The 'eventual' part can range from milliseconds to seconds, depending on the system and network load. This model is excellent for applications where immediate data freshness isn't critical (e.g., social media feeds, shopping cart items, cache data in Redis), providing high availability and lower latency at scale. Many NoSQL databases, including DynamoDB's default reads, Redis, and MongoDB's secondary reads, often leverage eventual consistency.

Key Takeaways

  • Strong Consistency: All reads see the most recent write; higher latency/lower availability.
  • Eventual Consistency: Reads might see stale data temporarily but eventually converge; higher availability/lower latency.
  • Choose consistency based on application needs: critical data often requires strong, while non-critical data can use eventual.
  • NoSQL databases often allow you to configure or choose the desired consistency level for specific operations.

Code Example

python
import boto3

dynamodb = boto3.client('dynamodb')

# Assuming 'Users' is a DynamoDB table with a 'UserId' primary key
user_id = 'some_user_id_123'

# Example of an Eventual Consistent Read (default in DynamoDB)
response_eventual = dynamodb.get_item(
    TableName='Users',
    Key={'UserId': {'S': user_id}}
)
print(f"Eventual Read Data: {response_eventual.get('Item')}")

# Example of a Strongly Consistent Read
response_strong = dynamodb.get_item(
    TableName='Users',
    Key={'UserId': {'S': user_id}},
    ConsistentRead=True # This flag ensures strong consistency
)
print(f"Strong Read Data: {response_strong.get('Item')}")

How this code works

This Python code demonstrates the practical difference between eventual and strong consistency when reading data from Amazon DynamoDB, a popular NoSQL database. It uses the boto3 library to interact with DynamoDB, first initializing a client. A specific user_id is defined, representing the primary key of an item to retrieve from a hypothetical 'Users' table. This setup prepares for two distinct data retrieval attempts to showcase the consistency models.

The first read, performed using dynamodb.get_item, is an eventual consistent read. Crucially, this is DynamoDB's default behavior for reads. It means the read might return data that hasn't fully propagated across all storage nodes if a write just occurred, prioritizing availability and lower latency. The second read explicitly requests strong consistency by setting the ConsistentRead=True flag within the get_item call. This ensures the read will always return the most up-to-date data, even if it takes slightly longer to complete, by confirming all relevant storage nodes have the latest version. Both reads then print the retrieved item data, allowing direct comparison of their results under specific timing conditions.