3

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;
    }
}
SetiSeeker
  • 6,482
  • 9
  • 42
  • 64

2 Answers2

2

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.

Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241
1

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

Jesse Squire
  • 6,107
  • 1
  • 27
  • 30
  • Hi Jesse, I am aware that the client doesn't support this, and 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. Thank you for your reply. – SetiSeeker Dec 15 '21 at 14:46
  • Hi Jesse I have updated the question to try and make it clearer, and thank you to the team for tracking my failover question. – SetiSeeker Dec 15 '21 at 14:55
  • Can you please edit your question and include code where you are using TableServiceClient? – Gaurav Mantri Dec 15 '21 at 14:56
  • @GauravMantri that code is in the original post as is the nuget package and verion – SetiSeeker Dec 15 '21 at 14:57
  • You included the code where you're configuring the dependency. I was more interested in seeing the code where you're injecting this dependency. It is quite clear that you will need 2 different TableServiceClient instances at least as of now - one pointing to the primary endpoint and other to secondary endpoint. – Gaurav Mantri Dec 15 '21 at 15:00
  • @GauravMantri Thank you for clarifying, I will add the code – SetiSeeker Dec 15 '21 at 15:04
  • Added an answer. Not sure if this is the best approach but it should give you some idea about how to proceed. Please do share the solution you come up. I would be very much interested in knowing about that. – Gaurav Mantri Dec 15 '21 at 15:27