Migrating from Azure Table Storage to Cosmos DB Table API is often framed as a simple upgrade, but the real win is unlocking drastically different performance characteristics and scaling behaviors.

Let’s see it in action. Imagine you have a classic Azure Table Storage account with a few tables. You’re hitting limitations on request throughput and latency under load.

Here’s a quick look at a typical "entity" in Azure Table Storage:

{
  "PartitionKey": "user123",
  "RowKey": "profile",
  "email": "test@example.com",
  "displayName": "Test User",
  "timestamp": "2023-10-27T10:00:00Z"
}

Now, let’s set up a Cosmos DB Table API account and create a "table" within it. The conceptual similarity is intentional, but the underlying engine is entirely different.

// Using Azure.Data.Tables NuGet package
var cosmosDbConnectionString = "AccountEndpoint=https://your-cosmos-db-account.documents.azure.com:443/;AccountKey=YOUR_PRIMARY_KEY;";
var client = new TableServiceClient(cosmosDbConnectionString);

// Create a table (if it doesn't exist)
var table = client.GetTableClient("MyCosmosTable");
await table.CreateIfNotExistsAsync();

// Insert an entity
var entity = new TableEntity("user123", "profile")
{
    { "email", "test@example.com" },
    { "displayName", "Test User" },
    { "timestamp", DateTimeOffset.UtcNow }
};
await table.AddEntityAsync(entity);

The primary driver for this migration isn’t just to get a new API endpoint; it’s to move from a storage system with provisioned throughput (and often noisy neighbors) to one with guaranteed, scalable throughput (Request Units or RUs) and lower, more predictable latency, especially for global distribution.

Internally, Cosmos DB Table API uses a distributed, multi-master architecture. When you provision RUs, you’re telling Cosmos DB how much processing power to allocate for your operations across its globally distributed nodes. This means that a GetEntity operation, which might have variable latency in Table Storage due to network hops and internal queuing, becomes a much more predictable operation in Cosmos DB, often measured in single-digit milliseconds, even across regions.

The core levers you control are the Throughput (RUs) and the Indexing Policy. While the Table API abstracts away much of the indexing complexity compared to the Core (SQL) API, Cosmos DB still indexes all properties by default for queries. This is a key difference: in Azure Table Storage, only PartitionKey and RowKey are indexed for efficient lookups. In Cosmos DB Table API, any property can be part of a query filter and benefit from indexing, dramatically changing query performance for non-key fields.

When you think about migrating, the most impactful decision is how you map your existing Azure Table Storage "tables" to Cosmos DB "tables" and, crucially, how you provision your Request Units (RUs). A common mistake is to just provision the minimum RUs and expect performance parity. You need to analyze your existing throughput and, more importantly, your peak throughput requirements to set an appropriate RU target. For example, if your Azure Table Storage account was handling 1000 transactions per second, you’ll need to provision enough RUs in Cosmos DB to match that, potentially much more if you’re aiming for higher performance or concurrent access from multiple regions.

The way Cosmos DB handles consistency levels also fundamentally changes behavior. While Azure Table Storage offers eventual consistency with strong consistency for reads immediately after writes to the same region, Cosmos DB offers a spectrum from Strong to Eventual, allowing you to trade off consistency for latency and throughput. For most Table API use cases, "Session" consistency is the default and offers a good balance, ensuring you read your own writes within a client session.

One of the most significant shifts in thinking, especially if you’re coming from a background where your data model is strictly dictated by PartitionKey and RowKey for query efficiency, is that Cosmos DB Table API allows you to query on any property with good performance, provided you’ve provisioned sufficient RUs. This means you can, and often should, rethink your data modeling to support broader query patterns without sacrificing performance. A common pattern is to denormalize data slightly, embedding related information within a single entity, knowing that Cosmos DB’s indexing will make those fields queryable.

The next step after mastering RUs and data modeling is understanding how to leverage the different consistency levels for optimized performance and cost.

Want structured learning?

Take the full Cosmos-db course →