I am using the latest Azure.Data.Tables
nuget package, version 12.3.0
to connect to Azure table storage in an ASP.NET Core C# Application.
My application needs to failover to a secondary region for reads if the primary region fails.
Currently the setup the of TableServiceClient
is done in the Startup.cs as follows:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(new TableServiceClient(new Uri("PrimaryRegionConnectionURL"), new DefaultAzureCredential()));
}
How do would I update the current instance of TableServiceClient
with an instance pointed to the secondary region? Is there a better approach to achieve this failover?
Just to Clarify:
I am aware that the client doesn't support failing over and the team have created a ticket to look at this feature in future.
I realize I need to have a new instance of TableServiceClient
.
I am just not sure how I would replace the one created at startup with a new instance pointed to the secondary instance at the time of failure.
Here is that code that consumes the TableServiceClient
public class TableRepository : ITableStorageRepository
{
readonly TableServiceClient _serviceClient;
public TableRepository(TableServiceClient serviceClient)
{
_serviceClient = serviceClient;
}
public async Task<ICollection<T>> GetPartitionEntities<T>(string partitionKey, string tableName)
where T : class, ITableEntity, new()
{
var listOfEntities = new List<T>();
var tableClient = _serviceClient.GetTableClient(tableName);
var queryResults = tableClient.QueryAsync<T>(filter => filter.PartitionKey == partitionKey);
await foreach (var row in queryResults)
{
listOfEntities.Add(row);
}
return listOfEntities;
}
}
CodePudding user response:
Automatic failover using the same client is not currently supported; to use the secondary region, a separate TableServiceClient
would be needed. More context is available here: https://github.com/Azure/azure-sdk-for-net/issues/25456
Work for adding support is being tracked here: https://github.com/Azure/azure-sdk-for-net/issues/25710
CodePudding user response:
Not sure if this is the best way to accomplish it, but this is how I would have done it considering I have to handle the logic of switching between primary and secondary endpoint myself.
First, I would have created two instances of TableServiceClient
- one for the primary and other for secondary.
public void ConfigureServices(IServiceCollection services)
{
Dictionary<string, TableServiceClient> tableServiceClients = new Dictionary()
{
"Primary", new TableServiceClient(new Uri("PrimaryRegionConnectionURL"), new DefaultAzureCredential()),
"Secondary", new TableServiceClient(new Uri("SecondaryRegionConnectionURL"), new DefaultAzureCredential())
}
services.AddSingleton(tableServiceClients);
}
Next, I would have extracted the logic for fetching the entities in a separate function and passed the client to that function (let's call that GetPartitionEntitiesImpl
).
Then in GetPartitionEntities
method I would have tried to get the entities from the primary endpoint and catch the exception. If the exception indicates primary endpoint failure, I would have called GetPartitionEntitiesImpl
function again and try to fetch the entities from the secondary endpoint.
public class TableRepository : ITableStorageRepository
{
readonly TableServiceClient _primaryServiceClient, _secondaryServiceClient;
public TableRepository(Dictionary<string, TableServiceClient> tableServiceClients)
{
_primaryServiceClient = tableServiceClients["Primary"];
_secondaryServiceClient = tableServiceClients["Secondary"];
}
public async Task<ICollection<T>> GetPartitionEntities<T>(string partitionKey, string tableName)
where T : class, ITableEntity, new()
{
try
{
return await GetPartitionEntitiesImpl(_primaryServiceClient, partitionKey, tableName);
}
catch (Exception exception)
{
//Check if there is a need for failover
if (shouldTrySecondaryEndpoint)
{
return await GetPartitionEntitiesImpl(_secondaryServiceClient, partitionKey, tableName);
}
}
}
private async Task<ICollection<T>> GetPartitionEntitiesImpl<T>(TableServiceClient serviceClient, string partitionKey, string tableName)
where T : class, ITableEntity, new()
{
var listOfEntities = new List<T>();
var tableClient = serviceClient.GetTableClient(tableName);
var queryResults = tableClient.QueryAsync<T>(filter => filter.PartitionKey == partitionKey);
await foreach (var row in queryResults)
{
listOfEntities.Add(row);
}
return listOfEntities;
}
}
Also, please take a look at the code of older Azure Storage SDK (version 9.x) regarding the logic for switching between primary and secondary endpoints. That SDK handles this scenario rather well.