Cosmos DB’s consistency levels aren’t a dial you turn for "more consistent" or "less consistent"; they’re fundamentally different guarantees about when you’ll see your writes.
Let’s see what happens when we try to read a document immediately after writing it, using different consistency levels.
from azure.cosmos import CosmosClient, PartitionKey
# Replace with your actual connection string and database/container details
CONNECTION_STRING = "YOUR_COSMOS_DB_CONNECTION_STRING"
DATABASE_ID = "myDatabase"
CONTAINER_ID = "myContainer"
client = CosmosClient.from_connection_string(CONNECTION_STRING)
database = client.get_database_client(DATABASE_ID)
container = database.get_container_client(CONTAINER_ID)
# --- Strong Consistency ---
print("--- Strong Consistency ---")
item_id = "strong-test-item"
initial_item = {"id": item_id, "value": "initial"}
container.upsert_item(initial_item)
print(f"Upserted item: {initial_item}")
# Read immediately with strong consistency
try:
read_item_strong = container.read_item(item_id, PartitionKey(item_id))
print(f"Read item (strong): {read_item_strong}")
except Exception as e:
print(f"Error reading item (strong): {e}")
# --- Session Consistency ---
print("\n--- Session Consistency ---")
item_id_session = "session-test-item"
initial_item_session = {"id": item_id_session, "value": "initial session"}
container.upsert_item(initial_item_session)
print(f"Upserted item: {initial_item_session}")
# Read immediately with session consistency (default)
try:
read_item_session = container.read_item(item_id_session, PartitionKey(item_id_session))
print(f"Read item (session): {read_item_session}")
except Exception as e:
print(f"Error reading item (session): {e}")
# --- Eventual Consistency ---
print("\n--- Eventual Consistency ---")
item_id_eventual = "eventual-test-item"
initial_item_eventual = {"id": item_id_eventual, "value": "initial eventual"}
container.upsert_item(initial_item_eventual)
print(f"Upserted item: {initial_item_eventual}")
# Read immediately with eventual consistency
# Forcing eventual consistency requires a specific client configuration or header.
# In the SDK, you can often achieve this by explicitly setting the consistency level
# for a specific operation if the client doesn't default to it.
# However, the most direct way is often via the REST API or by ensuring
# the client is *not* configured for stronger levels.
# For demonstration, we'll simulate a read that *might* be eventual.
# In a real scenario, you'd configure the client or use a specific API call.
# For simplicity here, we'll just do a regular read and acknowledge it *could* be eventual.
# A true test would involve multiple clients or delayed reads.
# Let's simulate a read that *could* be eventual by relying on the default or
# if the client were configured for it.
# In practice, to *guarantee* an eventual read for testing, you'd typically
# use a separate client or ensure the client's default is set to eventual.
# We'll perform a standard read and note its potential for eventual consistency.
try:
# To truly test eventual, you'd often need a delay or a separate client.
# For this example, we'll just perform a read and understand its behavior.
# In a real application, you'd configure your client's default consistency level.
# Example: client = CosmosClient(connection_string, consistency_level='Eventual')
read_item_eventual = container.read_item(item_id_eventual, PartitionKey(item_id_eventual))
print(f"Read item (eventual): {read_item_eventual}")
except Exception as e:
print(f"Error reading item (eventual): {e}")
Here’s what’s happening under the hood. Cosmos DB is a globally distributed database. When you write data, it doesn’t just go to one server; it needs to be replicated to multiple Azure regions. The consistency level dictates how many replicas must acknowledge the write before it’s considered "committed" and how quickly those acknowledgements propagate.
Strong Consistency: This is the gold standard. When you write data and then immediately perform a read using Strong consistency, you are guaranteed to see that write. This is because the write operation must be acknowledged by a quorum (a majority) of replicas in the current write region before the operation returns success. Reads then go to a replica that has already applied this write. The trade-off is higher latency for writes and reads, especially in geo-replicated scenarios, because coordination across multiple replicas takes time.
- Diagnosis: Check your
CosmosClientinstantiation. If you don’t specifyconsistency_level, it defaults toStrongif you are connected to a single region orBoundedStalenessotherwise. For explicit strong consistency, ensureconsistency_level='Strong'is set. - Fix: Instantiate your client like this:
client = CosmosClient(connection_string, consistency_level='Strong'). - Why it works: This forces all operations initiated by this client instance to adhere to the strictest read-after-write guarantee, ensuring that any data written by this client will be immediately readable by it.
Session Consistency: This is the default for Cosmos DB. It provides a weaker guarantee than Strong but stronger than Eventual. Within a single client session (defined by the lifetime of your CosmosClient instance), reads are guaranteed to be consistent with writes performed by that same client instance. If you write a document, then immediately read it using the same client, you will see your write. However, if another client instance (or even a different thread using the same client if not careful) reads the data, they might not see it immediately. This offers a good balance between performance and consistency for many applications.
- Diagnosis: Verify your
CosmosClientinstantiation. Ifconsistency_levelis not specified,Sessionconsistency is used if the default is notStrong(e.g., in a multi-region setup whereBoundedStalenessmight be the effective default). - Fix: Explicitly set
consistency_level='Session'during client instantiation:client = CosmosClient(connection_string, consistency_level='Session'). - Why it works: This ensures that the client instance tracks its own writes. Subsequent reads originating from this specific client instance will always fetch data that reflects its own prior writes, providing per-session guarantees.
Eventual Consistency: This is the fastest but weakest consistency level. When you write data, the operation returns quickly because it only needs to be acknowledged by the primary replica in the write region. However, it might take some time (typically milliseconds to seconds) for this write to propagate to all other replicas. If you read data using Eventual consistency, you might get stale data – data that hasn’t yet received the latest write. This is suitable for scenarios where seeing slightly older data is acceptable, such as displaying a count of items or showing a list that doesn’t need to be perfectly up-to-date.
- Diagnosis: Confirm your
CosmosClientis explicitly configured forEventualconsistency, or that your application logic is designed to tolerate potential stale reads if the default is something else. - Fix: Instantiate your client with
consistency_level='Eventual':client = CosmosClient(connection_string, consistency_level='Eventual'). - Why it works: By choosing
Eventual, you instruct the client to accept data from any available replica, even if that replica hasn’t yet received the latest write, prioritizing availability and low latency over immediate data freshness.
There’s also ConsistentPrefix and BoundedStaleness. ConsistentPrefix guarantees that reads will always return a prefix of the write history, meaning you’ll never see writes out of order, but you might still see stale data. BoundedStaleness allows you to specify a maximum staleness window (e.g., "I can tolerate data that is at most 5 seconds old"). Cosmos DB will return data from a replica that is at most that staleness away, offering a tunable balance between consistency and performance.
The most subtle aspect of session consistency is its per-client-instance nature. If you have multiple CosmosClient instances in your application, even if they connect to the same Cosmos DB account and use the same credentials, they do not share session state. A write performed by client_A might not be immediately visible to client_B if client_B performs a read before client_A’s write has propagated to the replica client_B is reading from, even if both clients are configured for session consistency.
The next challenge you’ll likely encounter is understanding how to manage BoundedStaleness effectively for geo-replicated scenarios.