Batch Operations in Azure Storage Tables

Azure Storage Tables enable you to perform multiple data operations in a single request. This is known as a batch operation. Batch operations can significantly improve performance by reducing the number of network round trips required to interact with your data. This document explores how to implement batch operations for inserting, updating, and deleting entities in Azure Storage Tables.

Important Considerations for Batch Operations:

  • A batch operation can contain up to 100 entities.
  • All entities in a batch operation must belong to the same partition.
  • Batch operations are atomic, meaning all operations succeed or fail together.
  • There are two types of batch operations:
    • Transaction batches: Enforce atomicity.
    • Non-transactional batches: Offer performance benefits by allowing independent operations within the batch, though they don't guarantee atomicity.

Types of Batch Operations

1. Transactional Batches

Transactional batches provide atomicity, ensuring that either all operations within the batch succeed, or none of them do. This is crucial when you need to maintain data consistency across multiple entities.

2. Non-Transactional Batches

Non-transactional batches are primarily for performance. While they group operations together to reduce network overhead, individual operations within the batch might succeed while others fail. They do not guarantee atomicity.

Implementing Batch Operations (using Azure SDK for .NET)

The following examples demonstrate how to perform batch operations using the Azure Storage SDK for .NET. The concepts are similar across different SDKs.

Inserting Multiple Entities

You can insert multiple entities in a single batch request. For transactional batches, you'll typically use a TableTransaction.


using Azure.Data.Tables;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

// Assume 'tableName' and 'partitionKey' are defined
// Assume 'tableClient' is an instance of TableClient connected to your table

var partitionKey = "MyPartition";
var entitiesToInsert = new List<MyEntity>
{
    new MyEntity { PartitionKey = partitionKey, RowKey = "1", Name = "Alice", Age = 30 },
    new MyEntity { PartitionKey = partitionKey, RowKey = "2", Name = "Bob", Age = 25 },
    new MyEntity { PartitionKey = partitionKey, RowKey = "3", Name = "Charlie", Age = 35 }
};

// For transactional batch
var transactions = new List<TableTransactionAction>();
foreach (var entity in entitiesToInsert)
{
    transactions.Add(new TableTransactionAction(TableTransactionActionType.Add, entity));
}

try
{
    await tableClient.SubmitTransactionAsync(transactions);
    Console.WriteLine("Transactional batch insert successful.");
}
catch (RequestFailedException ex)
{
    Console.WriteLine($"Batch insert failed: {ex.Message}");
}

// For non-transactional batch (inserting multiple entities as separate operations)
// This approach leverages the SDK's ability to batch requests efficiently.
// While not strictly a 'transactional' batch, it groups operations for performance.
try
{
    foreach (var entity in entitiesToInsert)
    {
        await tableClient.AddEntityAsync(entity);
    }
    Console.WriteLine("Non-transactional batch insert (individual adds) completed.");
}
catch (RequestFailedException ex)
{
    Console.WriteLine($"Batch insert (individual adds) encountered an error: {ex.Message}");
}
            

Updating Multiple Entities

Similarly, you can update existing entities within a batch.


// Assume 'tableClient' and 'partitionKey' are defined

var entitiesToUpdate = new List<MyEntity>
{
    new MyEntity { PartitionKey = partitionKey, RowKey = "1", Name = "Alice Smith", Age = 31 },
    new MyEntity { PartitionKey = partitionKey, RowKey = "2", Name = "Bob Johnson", Age = 26 }
};

// For transactional batch
var updateTransactions = new List<TableTransactionAction>();
foreach (var entity in entitiesToUpdate)
{
    updateTransactions.Add(new TableTransactionAction(TableTransactionActionType.UpdateMerge, entity));
}

try
{
    await tableClient.SubmitTransactionAsync(updateTransactions);
    Console.WriteLine("Transactional batch update successful.");
}
catch (RequestFailedException ex)
{
    Console.WriteLine($"Batch update failed: {ex.Message}");
}
            

Deleting Multiple Entities

Deleting entities in a batch is also straightforward.


// Assume 'tableClient' and 'partitionKey' are defined

var rowKeysToDelete = new List<string> { "3", "4" }; // Assuming RowKey "4" also exists

// For transactional batch
var deleteTransactions = new List<TableTransactionAction>();
foreach (var rowKey in rowKeysToDelete)
{
    // Note: For delete, you often need to provide the full entity or key for idempotency checks
    // For simplicity, assuming we know the keys. In real scenarios, fetch or use etag.
    deleteTransactions.Add(new TableTransactionAction(TableTransactionActionType.Delete, new TableEntity(partitionKey, rowKey)));
}

try
{
    await tableClient.SubmitTransactionAsync(deleteTransactions);
    Console.WriteLine("Transactional batch delete successful.");
}
catch (RequestFailedException ex)
{
    Console.WriteLine($"Batch delete failed: {ex.Message}");
}
            

Best Practices

Performance Tip:

Even if atomicity isn't strictly required, grouping multiple write operations into a single batch can yield significant performance gains due to reduced latency.