VSLive! Blog

Industry Insights, Information, and Developer News

Blog archive

Myth: Cosmos DB Is Expensive. Reality: Containers Aren't Tables

"We evaluated Cosmos DB and it's way too expensive."

I feel like I hear this a lot and it always surprises me. But I think I might have figured out the pattern and it's driven by relational database thinking in a document-centric database. Someone on the team started sketching out their database design, counted the tables, and then created one Cosmos DB container for each one.

Orders container. Customers container. Products container. LineItems container. Addresses container. Categories container. AuditLogs container. On and on.

Next they do some back-of-the-envelope math and figure that each container wants a minimum of 400 Request Units per second (RU/s) of provisioned throughput. That's the floor. So if you've got 40 tables and you created 40 containers, you're paying for 16,000 RU/s minimum and that starts looking like $1,400.00 a month. And that's just to turn it on -- never mind running it in production.

At that point, yeah. Cosmos DB looks absurdly expensive.

But that's not right -- and the problem is that you treated containers like tables.

Containers aren't tables.

What a Table Does
In SQL Server, a table has a fixed schema. Every row has the same columns. An Orders table has order columns. A Customers table has customer columns. You can't put an order row in the customers table -- the schema won't allow it. One entity type per table. That's the deal.

You do it this way because SQL Server (or any relational database) enforces that schema and enforces the relationships between the data within the schema.

If you're like me, you've been doing relational database design for so long that this is second nature. It's just the way you do things and that knowledge is in your bones. So you go to Cosmos DB and you just do what comes naturally...

...but it's wrong.

What a Container Actually Is
A Cosmos DB container is a bucket -- a bucket that holds JSON documents. That's it. It doesn't enforce a schema beyond the partition key and an id field. It doesn't care what shape your documents are. You can put an Order document and a Customer document in the same container, right next to each other, with completely different structures.

{
    "id": "order-001",
    "tenantId": "acme-corp",
    "entityType": "Order",
    "orderDate": "2026-05-15",
    "total": 149.99,
    "lineItems": [
        { "product": "Widget", "quantity": 3, "price": 49.99 }
    ]
}
{
    "id": "cust-001",
    "tenantId": "acme-corp",
    "entityType": "Customer",
    "name": "Alice Smith",
    "email": "[email protected]"
}

Same container. Different shapes. Cosmos doesn't care. It stores JSON documents -- trees of data -- and a tree can be any shape.

Now at first, this is going to feel super weird. Isn't that going to be a mess? Answer: no. And it's all because of that partition key. Partition key design is a long discussion but -- TLDR version -- I recommend a hierarchical partition key of tenantId then entityType.

The tenantId value says whose data it is. And the entityType describes the type of data.

Want all the orders? Filter by tenantId plus entityType == "Order". Want a specific customer info record? Filter by entityType == "Customer" and the customer's id. The container holds everything; your queries select what you need.

The Math Changes Dramatically
Let's redo that cost calculation.

The relational mental model: 40 entity types -> 40 containers -> 40 x 400 RU/s = 16,000 RU/s minimum. Expensive. About $1,400.00/month.

The document model: 40 entity types -> 1 container -> 400 RU/s minimum. Not expensive. Something between $3.50/mo and $35.00/mo depending on actual traffic.

That's not a tiny difference. $1,400.00 versus $35.00. That's a 40x difference in your minimum spend because of a mental model mismatch (aka, the "you're doing it wrong" tax).

Now, I'm oversimplifying a little. There are legitimate reasons to use more than one container -- if you have an entity type with dramatically different throughput needs (like a high-volume event stream that could starve your user-facing queries), giving it a separate container with its own throughput makes sense. Or if you need different indexing policies for different types of data. Or different time-to-live (TTL) policies.

But "more than one" usually means 2 or 3. Not 40. The default should be "everything in one container unless you have a specific reason to separate." Most of my applications run on a single container.

How Partition Keys Make This Work
If everything's in one container, how do you keep things organized? That's what the partition key is for.

My default partition key is hierarchical: /tenantId,/entityType. The first level groups documents by who owns them. The second level groups by what type they are. So all of Acme Corp's orders are in one logical partition, all of Acme Corp's customers are in another, and they're all in the same container.

Container: "app-data"
|- Partition: tenantId="acme-corp", entityType="Order"
|  |- order-001
|  |- order-002
|  '- order-003
|- Partition: tenantId="acme-corp", entityType="Customer"
|  |- cust-001
|  '- cust-002
'- Partition: tenantId="contoso", entityType="Order"
   |- order-004
   '- order-005

"Get all orders for Acme Corp" is a single-partition query -- fast and cheap. "Get a specific customer by id" is a point read -- the cheapest operation in Cosmos DB (1 RU for a 1 KB document). The data is organized by access pattern, not by entity type.

This is a fundamentally different way of thinking about data organization. In SQL Server, the table is the organizing unit. In Cosmos DB, the partition key is the organizing unit. Tables group by shape. Partition keys group by access pattern.

The Relational Instinct Is Strong
I get why people make this mistake. If you've spent years -- or decades -- working with relational databases, "one entity type per storage unit" is just how your brain works. It's not wrong. It's how relational databases are designed. But applying that instinct to Cosmos DB is like buying a filing cabinet for every type of document in your office instead of organizing by project or client.

The container is the filing cabinet. The partition key is the folder system. The documents go where they make sense for how you'll access them, not where they make sense based on what shape they are.

Try It Before You Do the Math
If you're evaluating Cosmos DB, try this before you calculate cost: design your data model with a single container, a hierarchical partition key of /tenantId,/entityType, and put everything in it. Then estimate your throughput based on your actual read/write patterns.

I think you'll find the number is a lot lower than 16,000 RU/s.

Go Deeper
I've written a Cosmos DB series on my blog -- Azure Cosmos DB for .NET Developers -- that covers data modeling, partition key design, query performance, cost optimization, and building production applications. If the "containers aren't tables" insight changed how you're thinking about Cosmos DB, the series goes a lot further.

About the Author

Benjamin Day is a consultant, trainer and author specializing in software development, project management and leadership.

Posted by Benjamin Day on 05/20/2026


Keep Up-to-Date with Visual Studio Live!

Email Address*Country*
Please type the letters/numbers you see above.