Azure Table Storage and pessimistic concurrency using an Azure blob


In my previous two blog posts, I have spoken about concurrency using Azure Blobs and Azure Table Storage.

Azure Blob Storage and managing concurrency

Azure Table Storage and managing concurrency

In the Azure Table Storage and managing concurrency, I state that they only option you have for concurrency is optimistic concurrency. This is when performing an update, it will verify if the data has changed since the application last read that data. However there is a way to perform pessimistic locking on an Azure table entity by assigning a designated blob for each table, and try to take a lease on the blob before operating the table.

This blog post will walk you through creating a solution that allows you to perform pessimistic locking for Azure table entity. My solution will show two methods. The first method is a single thread application that will try to update a number in the Azure table. If we run the program twice at the same time, one of the programs will be blocked and receive an HttpsStatusCode of 409 (Conflict).

You will need to following NuGet Packages installed for the code to work:

  • Microsoft.WindowsAzure.ConfigurationManager
  • WindowsAzure.Storage

The SingleThreadTableStorageUpdate() method will first obtain the values from the app.config. These values are:

  • BlobStorageFileName – The filename of the blob that will be assigned a lease.
  • BlobStorageContainerReference – The Blob container, that will hold the blob file.
  • TableStorageReference – The name of the Table within Azure Storage.
  • StorageConnectionString – The connection string to Azure Storage.
//Obtain the BlobStorage information
String filename = System.Configuration.ConfigurationManager.AppSettings["BlobStorageFileName"];
String blobStorageContainerRef = System.Configuration.ConfigurationManager.AppSettings["BlobStorageContainerReference"];
String blobStorageTableRef = System.Configuration.ConfigurationManager.AppSettings["TableStorageReference"];
String connectionString  = CloudConfigurationManager.GetSettings("StorageConnectionString");

//Instantiate the NextNumber class
NextNumber nextNumber = new NextNumber(filename, blobStorageContainerRef, blobStorageTableRef, connectionString);

Within the NextNumber Class when it is instantiated, it will check or create the Blob file and table entity.

class NextNumber
{
 private readonly CloudBlobContainer _leaseContainer;
 private readonly CloudTable _table;
 private readonly String _filename;

 public NextNumber(string filename, string blobContainerReference, string tableStorageReference, string storageConnectionString)
 {
   //Get Connection to Storage.
   CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnectionString);
   _filename = filename;

   //This creates a Blob
   CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
   _leaseContainer = blobClient.GetContainerReference(blobContainerReference);
   _leaseContainer.CreateIfNotExists();

   //This creates a table.
   CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
   _table = tableClient.GetTableReference(tableStorageReference);
   _table.CreateIfNotExists();

   try
   {
       //Get a reference to the blob.
       CloudBlockBlob blob = _leaseContainer.GetBlockBlobReference(String.Format("{0}.lck", _filename));
       if (!blob.Exists())
       {
           //Blob doesn't exist therefore create table entity.
           NextNumberEntity entity = new NextNumberEntity
          {
             PartitionKey = _filename,
             RowKey = "",
             NumberValue = 0
          };

          //Upload blob with some information in file
          blob.UploadText("Created on " + DateTime.UtcNow);
          //Insert entity into table.
         _table.Execute(TableOperation.Insert(entity));
       }
   }
   catch (Exception ex)
   {
       Console.WriteLine("Error happened " + ex.Message);
   }
 }
}

Once I know that the blob file and table actually exists, I’m then able to get the next number and update the entity in the table.

nextNumber.GetNextNumber()

Below is the code for the GetNextNumer() method. Within the code I have grabbed the blob property lease information and displayed it within the console. This is useful to see the state of the current blob object and see if there is a lease on it.

internal int GetNextNumber()
{
   //Get blob reference and display current lease information
   CloudBlockBlob blob = _leaseContainer.GetBlockBlobReference(String.Format("{0}.lck", _filename));
   blob.FetchAttributes();

   Console.WriteLine(String.Format("LeaseDuration = {0}", blob.Properties.LeaseDuration));
   Console.WriteLine(String.Format("LeaseState = {0}", blob.Properties.LeaseState));
   Console.WriteLine(String.Format("LeaseStatus = {0}", blob.Properties.LeaseStatus));

   //Acquire the lease for 30 seconds.
   string leaseId = blob.AcquireLease(TimeSpan.FromSeconds(30), Guid.NewGuid().ToString());
   var nextNumber = 0;
   Console.WriteLine();

   Console.WriteLine(String.Format("Aquired lease on blob ID: {0}", leaseId));
   Console.WriteLine();

   try
   {
        //Get and display current lease information
        blob.FetchAttributes();
        Console.WriteLine(String.Format("LeaseDuration = {0}", blob.Properties.LeaseDuration));
        Console.WriteLine(String.Format("LeaseState = {0}", blob.Properties.LeaseState));
        Console.WriteLine(String.Format("LeaseStatus = {0}", blob.Properties.LeaseStatus));

        //Retrieve the entity out of the Azure table.
        TableResult tableResult = _table.Execute(TableOperation.Retrieve<NextNumberEntity>(_filename, ""));
        NextNumberEntity entity = (NextNumberEntity)tableResult.Result;
        //Update the number
        entity.NumberValue++;
        //Add back into Azure table.
        _table.Execute(TableOperation.Replace(entity));
        nextNumber = entity.NumberValue;
        //Wait to extend the time this calling code hold the lease for (demo purposes)
        Thread.Sleep(TimeSpan.FromSeconds(10));
    }
    catch (Exception ex)
    {
        Console.Write("An error: " + ex.Message);
    }
    finally
    {
       //Release the blob.
       blob.ReleaseLease(AccessCondition.GenerateLeaseCondition(leaseId));
    }
    return nextNumber;
  }

Lastly I have a class that is my NextNumberEntity. This inherits Microsoft.WindowsAzure.Storage.Table.TableEntity.

class NextNumberEntity : TableEntity
{
     public int NumberValue { get; set; }
}

If I run the above code, it creates the blob file, the table, and updates the number from 0 to 1 in the table.

Above shows the leaseobject blob container, and the nextnumbers table.

Above shows the Cann0nF0dderDemo.lck file created within the leaseobject blob container.

Above shows the nextnumber table with the number value set to one.

Above shows the console window. You can see how before the lease was acquired, the LeaseState for the blob is Available with the LeaseStatus set as Unlocked. Soon as a lease was acquired, the LeaseState for the blob is Leased, with the LeaseStatus set to Locked. Lastly the console displays the next number which is one.

If I run two instances of the program and get them trying to acquire the lease at the same time, one errors with a conflict.

So how can I ensure that both instances run and eventually both get a number? I’ve written another method that is similar to the GetNextNumber method within the NextNumber class, called GetNextNumberWithDelay(). If a conflict is discovered then it retries the method, until I have obtained the next number.

internal int GetNextNumberWithDelay()
{
   //Get the blob reference
   CloudBlockBlob blob = _leaseContainer.GetBlockBlobReference(String.Format("{0}.lck", _filename));
   var nextNumber = 0;
   bool gotNumber = false;

   while (!gotNumber)
   {
       try
       {
         //Acquire the lease for 60 seconds.
         string leaseId = blob.AcquireLease(TimeSpan.FromSeconds(60), Guid.NewGuid().ToString());
         Console.WriteLine("Acquired Lease to update number");

         try
         {
              //Retrieve the entity out of the Azure table.
              TableResult tableResult = _table.Execute(TableOperation.Retrieve<NextNumberEntity>(_filename, ""));
              //Wait to extend the time this calling code hold the lease for (demo purposes)
              Thread.Sleep(TimeSpan.FromSeconds(10));

              NextNumberEntity entity = (NextNumberEntity)tableResult.Result;
              //Update the number
              entity.NumberValue++;

              //Add back into Azure table.
              _table.Execute(TableOperation.Replace(entity));
              nextNumber = entity.NumberValue;
              Console.WriteLine();

              Console.WriteLine(String.Format("The next number is: {0}", nextNumber));
          }
          catch (Exception inner)
          {
              Console.WriteLine("Another Error: " + inner.Message);
          }
          finally
          {
              //Release the blob
              blob.ReleaseLease(AccessCondition.GenerateLeaseCondition(leaseId));
              gotNumber = true;
           }
       }
       catch (Microsoft.WindowsAzure.Storage.StorageException se)
       {
           var response = se.RequestInformation.HttpStatusCode;
           if (response != null && (response == (int)HttpStatusCode.Conflict))
           {
               //A Conflict has been found, lease is being used by another process, wait and try again.
               Console.Write(".");
               Thread.Sleep(TimeSpan.FromSeconds(2));
           }
           else
           {
              throw se;
           }
        }
    }

   Thread.Sleep(TimeSpan.FromSeconds(3));
   return nextNumber;
}

Just to really test my code, instead of just calling the GetNumberWithDelay() just once, I’m going to uses Task threading and call it 5 times at once. This way if I run two instances of the program, I’m requesting 10 different numbers on 2 instances, 10 different threads. The lease on the blob will only allow one thread at a time to request a number, making all the other threads wait. Before I ran this, I reset the NumberValue in the Storage table back to 0.

//Run the GetNextNumber code 5 times at once. 
Task.WaitAll(new[]{
             Task.Run(() => nextNumber.GetNextNumberWithDelay()),
             Task.Run(() => nextNumber.GetNextNumberWithDelay()),
             Task.Run(() => nextNumber.GetNextNumberWithDelay()),
             Task.Run(() => nextNumber.GetNextNumberWithDelay()),
             Task.Run(() => nextNumber.GetNextNumberWithDelay())
});

When the code runs, one thread on one instance will obtain the lease, every other thread (on both instances) will have to wait, and will display a dot “.”

As you can see from above, both instances obtained 5 different numbers, however each instance didn’t receive 5 sequential numbers, and together, they never grabbed the same number twice.

This concludes my blog posts on Azure Blob/Table concurrency.

Code: http://1drv.ms/1JBoIek

Advertisements