(upbeat music)
- Welcome to the Azure Cosmos
DB Essentials Series, season two.
My name is Mustafa,
I'm a product marketing manager for Cosmos DB.
And in this episode, we're going to discuss
partition strategy and how to choose a partition key.
So if I'm a developer, maybe it's my first time working
in Cosmos DB or a NoSQL database in general.
I go into the portal, I create a container,
and then I'm hit with this mysterious partition key.
So what's going on behind the scenes in Cosmos DB
that needs a partition key?
Well, what's happening behind the scenes is Azure Cosmos DB
is a distributed database,
and the way it scales is rather than trying to scale
a single node from one core, to two cores,
to three cores, and higher and higher,
we just go and spread that workload across multiple nodes,
all the way up to all of the available nodes
on Azure if you'd like.
This allows us to scale
for all intents and purposes to infinity.
Now when we start to discuss how this affects
our partition strategy,
this is best understood in the context of this scenario.
Let's say that if I wanna scale a workload
across node one, node two, and node three,
and let's say I store my yellow data on node one,
my green data on node two,
and blue and purple data on node three.
Later when I query looking for my purple data
if I route the query to node one,
it's gonna come back and tell me it never found
what is was looking for
because that data is as we see here on node three.
A partition key is a placement hint for the query engine
telling Azure Cosmos DB how to route the data when written,
as well as how to find that data
when you do a followup read.
The next question you might have
is can we run queries without the use of a partition key?
And the answer is yes.
So let's say we have some query,
something general that says select X from Y where Z,
and I wanna find all of my purple data on node three.
In the absence of a partition key
we actually can solve this problem,
which is to run something called a fanout query.
What a fanout query does is node one and two
will know they don't have any relevant data in their index,
and so node three knowing that it does have
all purple data here in its index
will return all of that purple data.
Now, the next question you might have is,
but if fanout queries can still find the data,
why don't we recommend always running fan out queries?
And the caveat to fan outs is that they don't scale.
So let's say hypothetically each node can support
10,000 requests per second
and I wanna scale that up to 20,000 requests per second.
We should just split the 20,000 load
between the two nodes.
Fanout queries do not distribute the work
of looking for data,
they just ask every node if it has it.
This means we're back to a bottleneck situation,
where we're bound by the maximum amount
of throughput on a single node.
Now, the improvement on this design
is the introduction of the partition key.
We constantly across all cloud services
see the word scale on everything.
Now, there are three things we can scale
in the context of a database, reads, writes, and storage.
So let's go through the scaling each.
Starting with storage, it's really the easiest to scale.
To scale storage,
I store my data across, let's say, X nodes,
with no routing and just run fanout queries.
Now I've scaled storage, but the bottleneck becomes reads
because we now need to ask every node
where our data is in order to find it.
If we wanna scale reads,
I can simply replicate my data across each of those nodes.
Now it's not only stored all over the place,
but since there are copies of my data
across all of these nodes, it's actually easier to find.
But now writes become the bottleneck
because we have to write so much data
in order to be able to read it.
So let's try and scale writes.
The only way you can scale write throughput
is to shard or partition the data.
That means we divide up the write workloads
so that the writes on node one are separate
from the writes on node two.
If we remember that we replicated to scale reads,
well, now we also have to write the replicas
for each of those writes on other nodes,
so writes is actually the hardest thing to scale.
But back to our original point,
we've got a bunch of traffic.
Rather than having every request fan out to every node,
a better way to design the system with scalability in mind
is to route requests
only to their minimal number of relevant nodes.
And that's effectively what the partition key
allows us to do, it gives us a signal.
So for a more concrete example of this is,
let's say that I'm building a shopping cart
and my shopping cart,
I typically run queries based off of a user ID.
So let's say Alice here wants to know
what's in Alice's shopping cart,
Bob wants to know what's in Bob's shopping cart,
and Carol wants to know what's in hers as well.
What we can do is we can set the partition key
to the user ID.
That way, when we see in the where clause of a query
a where userID, it equals, let's, say Alice,
if Alice is the purple data set,
we now know exactly how to route to the machine
hosting Alice's data.
Now, with this idea of scale and partition keys
as a placement hint in mind,
let's talk about some best practices
for choosing a partition key.
So, there's a few design principles
when choosing a partition key.
First, you need to understand the ratio of reads to writes
for your application.
Now, if you're application is 90% reads,
then your partition key should optimize
for the top most frequently run queries
allowing you to efficiently route frequently used keywords.
If you're application is write heavy,
you should choose a partition key that prioritizes
load balancing overall request volume.
And third, if you're application needs ACID transactions,
then you should also be concerned
about the scope of your partition key,
but we'll discuss that in a future episode
if you guys wanna know.
This is typically done in a minority of use cases.
So let's go through each of these.
If we remember one of the first principles
that a partition key should load balance
overall request volume,
this is because we wanna avoid hotspots on a single node.
If I have a 20,000 requests per second,
and I choose the incorrect partition key
resulting in one node running at 100% utilization
and the other at zero,
this will bottleneck the throughput of our database
to the power of a single node, which is what we don't want.
A good example is an IoT application
with the partition key of the current time.
That is a bad key because if all these devices
send a health pulse at the same time,
we'll just rotate each node in our network being swamped,
one after the other as 10,000 devices
all ping one node routing by currentTimeID.
A better key would be to route
by using device ID as the partition key
because every device has a unique ID
and so each node can serve the message from the device ID,
or ID's which it should be routing for.
This is how you optimize for a write heavy workload.
Now, if you remember our other principle,
for a read heavy workload,
we wanna be able to route for efficiently run queries.
If you balance your overall request volume
and just make every request its own partition key,
so every write has its own unique partition key
via something like a unique GUID,
which is the primary key of a document,
this is not all that intuitive to query,
but you will effectively be able to load balance.
Now, on the other end,
if everything has the same partition key,
you'll be able to effectively query,
but you won't have any load balancing at all.
So to best do this,
you should look at your most frequently run queries,
and typically you'll find something in common between them,
which should then lead you to your partition key.
So, if we once again revisit the IoT scenario,
to find the best partition key here,
I might look at the topmost frequently run queries.
If in the where clause of my most run queries
I keep seeing the device ID show up,
then this would be the right partition key for my use case.
All right, so that concludes the third episode
about partition strategy,
and partition keys in Azure Cosmos DB.
Tune into the next episode on schema design
to better understand data modeling.
(upbeat music)