Azure Table Storage Concurrency Test and Importance of ETag Property

In this tutorial, I am going to show how Azure Table Storage entities are being protected for their data integrity during concurrent transactions. Table storage entities are using a property called ETag (included for every entity by default) to keep track of version of an entity. When a outdated Etag or an invalid ETag comes along with an update operation, table storage is going to throw an exception “412 – Pre-condition failed”. Table storage entities also provide an option to force update an entity if in case ETag is allotted a value of “*”.

As a first step create a Table Storage and add a table called “data”. Using Visual Studio 2013, I am adding one entity to it as shown below –

image

After data got added –

image

Now create an ASP.Net MVC 5 Web Application. Add the following entity definition to HomeController.cs (for simplicity sake I will be adding all the code of this tutorial in HomeController.cs).

    public class ConcurrentEntity : TableEntity
    {
        public ConcurrentEntity() { }
        public Int64 Id { get; set; }
    }

Add the following Table storage repository classes –

 public class Repository
    {
        public ConcurrentEntity GetVariableEntity()
        {
            CloudTable table = TableUtilities.GetTableClient.GetTableReference("data");
            TableQuery<ConcurrentEntity> query = new TableQuery<ConcurrentEntity>().
                                                Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "1"));
            ConcurrentEntity result = table.ExecuteQuery<ConcurrentEntity>(query).FirstOrDefault();
            return result;
        }

        public bool UpdateVariableEntity(ConcurrentEntity entity)
        {
            Int64 presentId = entity.Id;
            CloudTable table = TableUtilities.GetTableClient.GetTableReference("data");
            TableOperation replaceOperation = TableOperation.Replace(entity);
            table.Execute(replaceOperation);
            return true;
        }
    }

    internal class TableUtilities
    {
        public static CloudTableClient GetTableClient
        {
            get
            {
                CloudStorageAccount storageAccount = CloudStorageAccount.Parse("You storage connection string");
                CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
                return tableClient;
            }
        }
    }

Now have the following Controller actions defined –

        private Repository rep;
        public HomeController()
        {
            rep = new Repository();
        }
        public ActionResult Index()
        {
            // Read the entity
            ConcurrentEntity entity = rep.GetVariableEntity();
            return View(entity);
        }

        public ActionResult Submit(ConcurrentEntity entity)
        {
            try
            {
                // Update the entity
                entity.Id = entity.Id + 1;
                rep.UpdateVariableEntity(entity);
            }
            catch (Exception ex)
            {
                string[] errorCodes = new string[] { "412" };

                // For nornal errors
                if (!errorCodes.Any(s => ex.Message.ToString().Contains(s)))
                {
                    ViewBag.ConcurrencyMessage = "There was some strange storage error, please try again later";
                    return View("Index");
                }

                ViewBag.ConcurrencyMessage = "Get Latest Version of the Id, as the Id got updated in middle of your transaction";
                return View("Index");
            }
            return View();
        }
Index.cshtml as follows - 
@model AzureTableStorageConcurrencyDemo.Controllers.ConcurrentEntity

@{
    ViewBag.Title = "Variable Data";
}

<h2>Index</h2>

<div>
    <h4>Variable Data</h4>
    <hr />
    @if (!String.IsNullOrWhiteSpace(@ViewBag.ConcurrencyMessage))
    {
        <div>
            @ViewBag.ConcurrencyMessage
        </div>
    }
    else
    {
        using (Html.BeginForm("Submit", "Home"))
        {
            <dl class="dl-horizontal">
                <dt>
                    @Html.LabelFor(model => model.Id, "Last used Id : ")
                </dt>

                <dd>
                    @Html.DisplayTextFor(model => model.Id)
                </dd>


                <dt>
                    @Html.LabelFor(model => model.ETag, "Present ETag : ")
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.ETag)
                </dd>


                <dt>
                    @Html.LabelFor(model => model.Timestamp, "Present TimeStamp : ")
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Timestamp)
                </dd>

            </dl>

            @Html.HiddenFor(model => model.Id)
            @Html.HiddenFor(model => model.ETag)
            @Html.HiddenFor(model => model.Timestamp)
            @Html.HiddenFor(model => model.PartitionKey)
            @Html.HiddenFor(model => model.RowKey)

            <input type="submit" value="Submit" />

        }
    }


</div>
Finally Submit.cshtml - 
@{
    ViewBag.Title = "Submit";
}

<h2>Submit</h2>
<div>
    Id Updated
</div>

To summarize we first created a Table Storage and created a table in it. We then added one entity with PartitionKey and RowKey as 1 and having a property Id which is also assigned with 1. We implemented our Table Storage repository along with its model in HomeController.cs.

As the first step of completing the test workflow, we have defined an Index Action in Home Controller, which will simply get the entity and display it on to the page, on the same page ETag property along with other properties are also maintained in hidden fields for restoring their values on model binding over submit button click. If we do not do that, we will get null values in those properties and at the time of update we get a argument exception “Replace requires an ETag (which may be the ‘*’ wildcard)”. So we maintain the state of the entity through hidden fields. If the update happens successfully, then a Submit view will be displayed with a success message.

We have to implement following workflow to test concurrent updates with the help of ETag property.

  1. User 1 open chrome browser navigates to the Index view.
  2. User 2 open IE browser navigates to the Index View.
  3. User 1 clicks submit button and updates the record. Update will be successful.
  4. User 2 clicks submit button and he will be notified that record got changed in middle of his transaction. No update will be done. This behavior is due to the fact that ETag has been changed because of User 1 record update. As User 2 will carry on with a outdated ETag, table storage is going to return a Pre-Condition failure error.

Lets check it in action – For Steps 1, 2 –

image

For Step 3 –

image

For Step 4 –

image

 

Now lets check what happens when we override ETag to “*”. Add this line of code to the Submit Action – entity.ETag = "*";. And re-run the test workflow. When we check the 4th step of workflow, we have following result. So both the update operations are successfully completed because ETag with “*” will force override an entity in table storage.

image

This completes our present tutorial, in the next set of tutorials we will get to know more about retry mechanisms which are available for transient errors and Blob Storage concurrency along its implementations. Happy Coding!!!

You may also like...

One Pingback/Trackback

    15 January 2014 at 10:01am
    [...] Azure Table Storage Concurrency Test and Importance of ETag Property ...
  • TechNet Blogs