Thursday, 3 January 2019

Using the Retry Pattern with Azure Storage from Xamarin.Forms

Back in 2017 I wrote about transient fault handling in Xamarin.Forms applications with the retry pattern. Transient faults include the momentary loss of network connectivity to services, the temporary unavailability of a service, or timeouts that arise when a service is busy. These faults can have a huge impact on the perceived quality of an application. Therefore, applications that communicate with remote services should ideally be able to:

  1. Detect faults when they occur, and determine if the faults are likely to be transient.
  2. Retry the operation if it’s determined that the fault is likely to be transient, and keep track of the number of times the operation is retried.
  3. Use an appropriate retry strategy, which specifies the number of retries, the delay between each attempt, and the actions to take after a failed attempt.

This transient fault handling can be achieved by wrapping all attempts to access a remote service in code that implements the retry pattern.

Traditionally, the typical approach to using the retry pattern with Azure Storage has been to use a library such as Polly to provide a retry policy. However, this isn’t necessary as the Azure Storage SDK includes the ability to specify a retry policy.The SDK provides different retry strategies, which define the retry interval and other details. There are classes that provide support for linear (constant delay) retry intervals, and exponential with randomization retry intervals. For more information about using Azure Storage from a Xamarin.Forms application, see Storing and Accessing Data in Azure Storage.

Retry policies are configured programmatically. When writing to/reading from blob storage, this can be accomplished by creating a BlobRequestOptions object and assigning to the DefaultRequestOptions property of the CloudBlobClient object:

public class AzureStorageService : IAzureStorageService { CloudStorageAccount _storageAccount; CloudBlobClient _client; public AzureStorageService() { _storageAccount = CloudStorageAccount.Parse(Constants.StorageConnection); _client = CreateBlobClient(); } CloudBlobClient CreateBlobClient() { CloudBlobClient client = _storageAccount.CreateCloudBlobClient(); client.DefaultRequestOptions = new BlobRequestOptions { RetryPolicy = new ExponentialRetry(TimeSpan.FromSeconds(3), 4), LocationMode = LocationMode.PrimaryThenSecondary, MaximumExecutionTime = TimeSpan.FromSeconds(20) }; return client; } ... }

This code creates a retry policy that uses an exponential retry. The arguments to the ExponentialRetry constructor specify the back-off interval between retries, and the maximum number of retry attempts (4). A maximum execution time of 20 seconds is set for all potential retry attempts. The LocationMode property is used to indicate which location should receive the request, if the storage account is configured to use geo-redundant storage. Here, PrimaryThenSecondary specifies that requests are always sent to the primary location first, and if a request fails, it’s sent to the secondary location. Note that if you use this option you must ensure that your application can work with data that may be stale if the replication from the primary store hasn’t completed.

All operations with the CloudBlobClient object will then use the specified request options. For example, the following code, which uploads a file to blob storage, will use the retry policy defined in the CloudBlobClient.DefaultRequestOptions property:

public async Task<string> UploadFileAsync(ContainerType containerType, Stream stream) { var container = _client.GetContainerReference(containerType.ToString().ToLower()); await container.CreateIfNotExistsAsync(); var name = Guid.NewGuid().ToString(); var fileBlob = container.GetBlockBlobReference(name); await fileBlob.UploadFromStreamAsync(stream); return name; }

Choosing whether to use the linear retry policy or the exponential retry policy depends upon the business needs of your application. However, a general rule of thumb is to use a linear retry policy for interactive applications that are in the foreground, and to use a exponential retry policy when your application is backgrounded, or performing some batch processing.

The retry policies provided by the Azure Storage SDK will be sufficient for most applications. However, if there’s a need to implement a custom retry approach, the existing policies can be extended through the IExtendedRetryPolicy interface.

For retry guidance with Azure Storage, see Azure Storage.

The sample this code comes from can be found on GitHub.