CosmosDb, know your partition costs, well more or less
In my previous post Cosmos Db know your costs, and remember I made the point that by understanding RU costs early, you can make informed decisions in relation to document design and application CRUD and query operations.
While it is easy and most certainly useful to arrive at a projected RU cost, using for example, the Request Units (RU) and Data Storage calculator or directly against a fixed 10GB collection via the Azure Portal (incidentally the same costs), the problem is these do not highlight RU costs when partitioning is required to support scale-out .
Now if you know your data (and index) size) will remain within the 10gb limit and you have sufficient headroom at 10,000 RU/sec, then a fixed 10GB collection will get you a long way and read no further. However, I have not seen enough small JSON documents out there in the wild, and so if you have a write cost alone of 10 RU then you can sustain at most 1,000 writes/sec, not including reads, updates or deletes. One can therefore imagine a fixed collection getting squeezed in the near term.
To keep this post short and to the point I will first gather baseline RU costs for partitioned collections. Costs are collected in the same way by:
- creating the required collection
- create a GUID and assign as the document id
- create a GUID and assign as the document partition key
- run CRUD and Query operations.
I wanted to gather baseline RU costs for collections with 2, 5 & 10 partitions. However, that is not currently how Cosmos Db works. Currently (and in general) a collection is created with 10 physical partitions and the selected RU/sec evenly distributed across these partitions. For example, a 30,000 RU collection will have 10 physical partitions and 3,000 RU/partition.
Instead I will look at RU costs for CRUD and query operations for a known document against collections of 2,500, 12,500 & 25,000 RU/sec. First, however, let’s have a recap of RU costs for the known document from my previous post.
And below are the RU costs for the same document in a partitioned world.
What is immediately apparent is
- RU costs for a partitioned collection for CRUD operations, and query operations where the partition key is known are the same as for a fixed 10GB collection.
- RU costs for a partitioned collection for CRUD operations, and query operations where the partition key is known remain the same as the collections scale up/out.
- Cross partition queries increase RU cost and seem variable
- Cross partition queries are not supported when secondary indexing is disabled. Note that if the request header x-ms-documentdb-query-enable-scan was set then an index scan would be enabled and the query would succeed, and obviously increase RU cost.
Cross partition queries increase RU cost and seem variable?
At face value the right-most column Query cost (RU) cross partition implies this is the case. Low query cost at 2,500 RU. Clearly increased cost at 12,500 RU, and then lower costs again at 25,000 RU. Really?
It turns out there are two separate things at play here.
Earlier I stated that in general a partitioned collection is created with 10 underlying physical partitions. This is always the case when a collection is created through the Azure Portal. The costs presented below are for similar collections created through the Azure Portal, showing similar variable RU costs for cross partition queries. However, the results above are for collections created via the REST API, and it turns out a partitioned collection with an RU between 2,500 and 10,000 is created with a single underlying partition. It is a partitioned collection, it just has one to begin with, and this is why the cross partition query RU cost is low and will remain low until the collection is scaled up/out.
The nature of the test
The nature of the test is to write a document with a different GUID as id for each run and record the RU cost for CRUD and query operations. Since the document id is random, a document can exist in any partition. In most cases (except for providing a partition key) the test remained unchanged. However, to support cross partition queries the query feed options RequestContinuation or MaxDegreeOfParallelism had to be set and to illustrate a point I went with the former.
Using the continuation token the test reissues the query until the document is found, sometimes early and sometimes not, as shown in the sample test metrics output below. In reality one should implement the MaxDegreeOfParallelism option, which searches all partitions and has a higher but fixed RU cost.
What are the key takeaways?
Cross partition queries are expensive and you need to model these early. The question must always be, is the cross partition query really necessary? Is there a design flaw or alternative approach?
The other key take away is to ensure the target collection of the costs analysis closely models target environment. If you operate in an environment where everything is delivered through code then configure the costing collection through code or at least recognise there are subtle differences between collections configured through the REST API and the Azure Portal. It is best not to get hung up about this. The fact is Cosmos Db (as with all things Azure) is continuously evolving and these features will appear first in the Azure Portal. Every effort is being made to surface these changes in the REST API, but this is harder and slower, simply for reasons of backward compatibility and breaking change.