Cosmos DB’s SDKs offer a way to batch operations, but it’s not a single "batch" API call; it’s a collection of operations that are sent together.

Let’s see it in action with a Python example. Imagine we have a container named users in a database mydatabase, and we want to add three new users:

from azure.cosmos import CosmosClient, PartitionKey

# Replace with your actual connection string and container details
endpoint = "YOUR_COSMOS_DB_ENDPOINT"
key = "YOUR_COSMOS_DB_KEY"
database_name = "mydatabase"
container_name = "users"

client = CosmosClient(endpoint, credential=key)
database = client.get_database_client(database_name)
container = database.get_container_client(container_name)

# Define the operations
operations = [
    {
        "operationType": "Create",
        "resourceBody": {
            "id": "user1",
            "name": "Alice",
            "email": "alice@example.com",
            "partitionKey": "alice"
        }
    },
    {
        "operationType": "Upsert",
        "resourceBody": {
            "id": "user2",
            "name": "Bob",
            "email": "bob@example.com",
            "partitionKey": "bob"
        }
    },
    {
        "operationType": "Replace",
        "resourceBody": {
            "id": "user3",
            "name": "Charlie",
            "email": "charlie@example.com",
            "partitionKey": "charlie"
        }
    }
]

# Execute the batch
try:
    results = container.execute_batch(operations, partition_key_string="some_partition_key_if_all_same")
    for result in results:
        print(f"Operation Status: {result.status_code}")
        if result.resource_body:
            print(f"  Resource ID: {result.resource_body.get('id')}")
except Exception as e:
    print(f"An error occurred: {e}")

This code snippet demonstrates how to define a list of operations – Create, Upsert, and Replace in this case – and then send them to Cosmos DB in a single request using execute_batch. The resourceBody contains the actual data for each item, and operationType specifies the action.

The core problem execute_batch solves is reducing network round trips and improving efficiency when performing multiple related operations. Instead of sending each create_item, upsert_item, or replace_item as a separate HTTP request, they are bundled into one. This is especially beneficial for high-throughput scenarios or when dealing with many small operations. Cosmos DB then processes these operations atomically within the scope of the batch, meaning either all operations succeed, or none of them do (though individual operations within a successful batch can still fail, returning specific error codes).

The operationType field is crucial here. It can be one of Create, Upsert, Replace, Delete, or Read. Create will fail if the item already exists. Upsert will create the item if it doesn’t exist or replace it entirely if it does. Replace will fail if the item doesn’t exist. Delete will remove an item, and Read will fetch an item.

When you execute a batch, Cosmos DB requires a partition_key_string if all operations in the batch target items within the same partition. If your batch operations span multiple partitions, you’ll need to execute separate batches for each partition. This is a fundamental constraint of how Cosmos DB distributes data. You can’t, for instance, create one item in partition "A" and another in partition "B" within a single execute_batch call. The SDK will raise an error if you attempt this.

The results returned by execute_batch is a list, where each element corresponds to an operation in the input list. Each result object contains a status_code indicating success or failure for that specific operation, and a resource_body if the operation was a Create, Upsert, or Read and was successful.

A common misconception is that execute_batch guarantees ACID transactions across all operations. While it provides atomicity for the batch request itself (meaning the request is processed as a single unit), individual operations within the batch can still fail. For example, if you try to Create an item that already exists, that specific operation will fail, but other operations in the batch might still succeed. The execute_batch method returns a list of results, allowing you to inspect the outcome of each individual operation.

The most surprising thing about execute_batch is that even though it reduces network latency by sending a single request, the RU (Request Unit) cost is the sum of the RUs for each individual operation within the batch, plus a small overhead. You aren’t getting a discount on RUs for batching; you’re paying for the work done, just more efficiently.

The next concept to explore is handling transactional batches, which are a more powerful form of batching that does guarantee atomicity across all operations within the batch, ensuring that either all operations succeed or none do, and they are executed serially.

Want structured learning?

Take the full Cosmos-db course →