Azure Table Storage vs Cosmos DB for Table
Azure has two ways to store table data, and they're deliberately compatible: the same data model, the same create, delete, update, and query operations through their SDKs. That compatibility is genuinely useful — applications written against Table Storage migrate to the Cosmos DB Table API with few code changes. It also makes the choice easy to get wrong, because the two services share an interface while differing substantially in what they guarantee.
Azure Table Storage
Table Storage is the basic, fast, and cost-effective option for structured data that doesn't need relational features. It's a key-attribute store with a schema-less design, so it adapts to changing application needs over time without migrations — a real contrast with anything requiring a predefined schema.
It stores large amounts of structured, non-relational data, tables grow automatically to meet demand, and it can be connected to and fully authenticated from both inside and outside Azure — supporting internet users connecting to applications and Microsoft Entra accounts accessing them.
Where it fits: storing terabytes of structured data reachable from web applications, and any flat-level storage need. It suits simple data requirements — no complex joins, foreign keys, or stored procedures.
Where it doesn't: if you need genuine relational features, use SQL Database. It's more capable and more configurable, and fighting Table Storage's model to produce relational behaviour is the wrong trade every time.
Microsoft's own training material sometimes describes Table Storage as "part of Azure Cosmos DB". That's loose enough to mislead. Table Storage is its own service inside a storage account, billed at storage-account rates. Cosmos DB separately offers a Table API that speaks the same protocol with Cosmos DB's throughput and distribution model behind it. Two products, one wire format.
The comparison that decides it
| Feature | Azure Table Storage | Azure Cosmos DB Table API |
|---|---|---|
| Latency | Fast, but no upper bound | Single-digit millisecond, SLA-backed, at any scale |
| Throughput | Variable throughput model; a 20,000 transactions per second target per storage account, and 2,000 entities per second per partition | Dedicated reserved throughput per table, scaling to millions of operations per second |
| Global distribution | Single region, with one optional readable secondary | Turnkey global distribution to any Azure region |
| Indexing | Primary index on PartitionKey and RowKey only; no secondary indexes |
Automatic and complete indexing on all properties, no index management |
| Query | Uses the index for the primary key, scans otherwise | Takes advantage of automatic indexing on all properties |
| Consistency | Strong within the primary region | Five well-defined consistency levels trading off availability, latency, throughput, and consistency |
| Pricing | Consumption-based | Consumption-based or provisioned capacity |
| SLA | 99.99% availability | 99.99% for single-region and multi-region with relaxed consistency; 99.999% read availability on multi-region accounts |
Reading the table
Four rows carry most of the decision.
Latency has no upper bound in Table Storage. It's fast in practice, but there's no guarantee — no percentile commitment, nothing to design an SLA against. If your application needs to promise a response time, Table Storage can't help you keep that promise.
Indexing is the one that bites hardest. Table Storage indexes PartitionKey and RowKey and nothing else. Any query that filters on another property scans, and the scan cost grows with the table. That constraint has to be designed around from the start — it dictates your partition key strategy, and retrofitting a different key scheme onto a populated table is a migration, not a change. Cosmos DB indexes every property automatically, which removes the constraint and the design work along with it.
Throughput has a ceiling. The target is 20,000 transactions per second across the whole storage account (assuming 1-KiB entities) and 2,000 entities per second on any single partition. That's generous for many applications and insufficient for others, and a hot partition reaches its limit long before the account does. Cosmos DB takes the opposite approach: you provision throughput for the table rather than inheriting a shared account-wide target.
Global distribution is binary. Table Storage gives you one region plus an optional readable secondary; Cosmos DB gives you turnkey replication to any Azure region. If a multi-region write topology is anywhere in the roadmap, only one of these two gets you there.
Choosing
Use Table Storage when the workload is straightforward key-value lookup at moderate scale, cost matters more than latency guarantees, queries are naturally expressed against the partition and row keys, and a single region is enough.
Use the Cosmos DB Table API when you need latency guarantees rather than good typical performance, queries filter on properties beyond the keys, throughput will exceed the storage account's 20,000 transactions per second target, or the application needs to serve more than one region.
The migration path exists in one direction and is cheap, which is worth knowing when the answer isn't obvious. Both share the data model and SDK operations, so starting on Table Storage and moving to the Cosmos DB Table API later requires few code changes. Starting on Table Storage is a defensible default when scale is uncertain — provided the partition key design doesn't paint you into a corner in the meantime, because that part doesn't migrate for free.
The broader Cosmos DB picture — the other APIs, capacity modes, availability tiers, and their cost multipliers — is covered in the post on Cosmos DB APIs and request units.