Cosmos DB doesn’t actually charge you for cross-partition queries; it charges you for the total Request Units (RUs) consumed by all partitions involved in a query.

Let’s see this in action. Imagine a products container with a categoryId partition key. We want to find all products in category 'electronics'.

// Request to get products in category 'electronics'
POST https://your-cosmos-db-account.documents.azure.com/dbs/your-db/colls/products/docs

{
    "query": "SELECT * FROM c WHERE c.categoryId = 'electronics'"
}

If 'electronics' is a popular category, it might span multiple physical partitions. Cosmos DB will query each of these partitions. The total RUs consumed will be the sum of RUs from each partition.

If 'electronics' is a single partition, the RU cost is just that partition’s cost. The surprise is that a query hitting one partition can be more expensive if that partition is heavily loaded with data or if the query is complex, than a query that appears to span many partitions but where each partition has very little data or the query is simple.

The core problem is that a single logical query in Cosmos DB often translates into multiple physical operations across different partitions. When your query’s WHERE clause doesn’t align with your partition key, Cosmos DB has to scan every partition to find matching documents.

Consider a users container partitioned by tenantId. If you query for a user by userId without including tenantId in your WHERE clause, Cosmos DB must scan all partitions.

// Inefficient query: scans all partitions
POST https://your-cosmos-db-account.documents.azure.com/dbs/your-db/colls/users/docs

{
    "query": "SELECT * FROM c WHERE c.userId = 'user123'"
}

If you have 100 physical partitions, and each document costs 1 RU to read, and you have 1000 documents per partition, this query will cost approximately 100 partitions * 1000 documents/partition * 1 RU/document = 100,000 RUs.

The fix is to ensure your queries can be routed to a single partition. This is achieved by including the partition key in your WHERE clause.

For our users container, if we know the tenantId (e.g., 'tenantA'), the query becomes efficient:

// Efficient query: routed to a single partition
POST https://your-cosmos-db-account.documents.azure.com/dbs/your-db/colls/users/docs

{
    "query": "SELECT * FROM c WHERE c.tenantId = 'tenantA' AND c.userId = 'user123'"
}

This query will only hit the partition(s) containing data for 'tenantA'. If 'tenantA' resides in a single partition, the cost is dramatically reduced, perhaps to just 1000 RUs (1000 documents * 1 RU/document).

Here’s how to diagnose and optimize:

  1. Identify Cross-Partition Queries: Use the Azure portal’s "Analyze query" feature or inspect query metrics in your application logs. Look for x-ms-continuation headers in the response, which often indicate that a query spanned multiple pages of results within a partition, but the real killer is when the initial request has to go to multiple partitions. The RequestCharge metric in the response is your primary indicator of cost. A high RequestCharge on a simple SELECT * is a red flag.

  2. Review Your Partition Key: Is it granular enough? If you have very few distinct values (e.g., true/false for a flag, or a handful of status codes), you’re likely creating "hot" partitions that will be expensive to query. A good partition key distributes requests and data evenly across many physical partitions. For example, using userId or sessionId is often a good choice for high-volume, low-latency scenarios.

  3. Rewrite Queries: Always filter by your partition key. If you must query across partitions, consider if a different data model or a different service (like Azure Synapse Analytics for large-scale analytics) is more appropriate. For example, if you frequently query across tenantId for reporting, denormalizing some data into tenant-specific containers or using a read-optimized data store might be better.

  4. Leverage id for Single Document Reads: If your goal is to fetch a single document by its unique id, and id is not your partition key, you must include the partition key in the WHERE clause. If id is your partition key, then fetching by id is inherently a single-partition operation.

  5. Batching and IS_DEFINED: If you have a list of userId values and you want to fetch them, and userId is not your partition key, avoid individual queries. Instead, use a single query with c.tenantId IN ('tenantA', 'tenantB') AND c.userId IN ('user123', 'user456'). This still requires the partition key to be present. If you don’t know the partition key value at query time, Cosmos DB will have to scan all partitions. If your partition key is complex (e.g., a composite key), ensure your query properly references all parts.

  6. Indexing Strategy: While not directly about cross-partition routing, an inefficient indexing strategy can inflate the RU cost within each partition that is scanned. Ensure you only index fields you actually query and that your index types (range, hash, spatial, composite) are appropriate. An index on categoryId will make WHERE c.categoryId = 'electronics' fast within a partition.

The most impactful optimization is often understanding that Cosmos DB is a distributed NoSQL database and its performance characteristics are tied to its distributed nature. Treating it like a relational database with global indexes on every column will lead to unexpected RU costs. The system is designed for high throughput by partitioning data, and your queries must leverage that partitioning to be cost-effective.

The next challenge you’ll face is optimizing the RUs within a single partition when a query is correctly routed.

Want structured learning?

Take the full Cosmos-db course →