Skip to content

Optimistic Concurrency in Azure Cosmos DB

I’ve started working on new side project using ASP.NET Core.  I wanted to try a new datastore and decided to give Azure Cosmos DB a try.

This project isn’t doing anything complicated but does contain enough real world use cases that can give me an idea of how the API works.

Concurrency

The first thing I needed to implement was how to handle concurrency.  Specifically optimistic concurrency.

In an optimistic concurrency model, a violation is considered to have occurred if, after a user receives a value from the database, another user modifies the value before the first user has attempted to modify it.

This is pretty typical when dealing with multi user environments like a web application.  Specifically in my case is:

  • Fetching out a document from Azure Cosmos DB
  • Mutating the data of the document
  • Sending the document back to to Azure Cosmos DB to be replaced

In my web application, the concurrency issue arises if the same document is being modified by multiple users at the same time.

Without having any type of concurrency, we are what is called a “Last Wins” mode.  Meaning, the last process/user that sends the document back to Azure Cosmos DB is what will be persisted.

ETags

Each document within Azure Cosmos DB has an ETag Property.

The ETag or entity tag is part of HTTP, the protocol for the World Wide Web. It is one of several mechanisms that HTTP provides for web cache validation, which allows a client to make conditional requests.

You may be familiar with ETag’s related caching.  A typical scenario is a user makes an HTTP request to the server for a specific resource.  The server will return the response along with an ETag in the response header.  The client then caches the response along with the associated ETag.

ETag: "686897696a7c876b7e"

If they client then makes another request to the same resource, it will pass a If-Non-Match header with the ETag it received.

If-None-Match: "686897696a7c876b7e"

If the resource has not changed and the ETag represents the current version, then the server will return a 304 Not modified status.  If the resource has been modified, it will return the appropriate 2XX status code along with the content new ETag header.

AccessCondition

Azure Cosmos DB uses ETags for handling optimistic concurrency.  When we retrieve a document from Azure Cosmos DB, it always contains an ETag property as apart of our document.

When we then want to send our request to replace a document, we can specify an AccessCondition with the ETag we received when we fetched out our document.

If the ETag we send is not current, the server will return a 412 Precondition Failed status code.  In our .NET SDK, this is wrapped up in a DocumentClientException.

Here is a full an example.

using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Shouldly;
using Xunit;
namespace Demo
{
public class OptimtimisticConcurrencyTests
{
private readonly DocumentClient _client;
private const string EndpointUrl = "https://localhost:8081";
private const string AuthorizationKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
private const string DatabaseId = "ConcurrencyDemo";
private const string CollectionId = "Customers";
public OptimtimisticConcurrencyTests()
{
_client = new DocumentClient(new Uri(EndpointUrl), AuthorizationKey);
}
[Fact]
public async Task Should_Throw_With_PreconditionFailed()
{
// Setup our Database and add a new Customer
var dbSetup = new DatabaseSetup(_client);
await dbSetup.Init(DatabaseId, CollectionId);
var addCustomer = new Customer(Guid.NewGuid().ToString(), "Demo");
await dbSetup.AddCustomer(addCustomer);
// Fetch out the Document (Customer)
var document = (from f in dbSetup.Client.CreateDocumentQuery(dbSetup.Collection.SelfLink)
where f.Id == addCustomer.Id
select f).AsEnumerable().FirstOrDefault();
// Cast the Document to our Customer & make a data change
var editCustomer = (Customer) (dynamic) document;
editCustomer.Name = "Changed";
// Using Access Conditions gives us the ability to use the ETag from our fetched document for optimistic concurrency.
var ac = new AccessCondition {Condition = document.ETag, Type = AccessConditionType.IfMatch};
// Replace our document, which will succeed with the correct ETag
await dbSetup.Client.ReplaceDocumentAsync(document.SelfLink, editCustomer,
new RequestOptions {AccessCondition = ac});
// Replace again, which will fail since our (same) ETag is now invalid
var ex = await dbSetup.Client.ReplaceDocumentAsync(document.SelfLink, editCustomer,
new RequestOptions {AccessCondition = ac}).ShouldThrowAsync<DocumentClientException>();
ex.StatusCode.ShouldBe(HttpStatusCode.PreconditionFailed);
}
}
}

Demo Source Code

I’ve put together a small .NET Core sample with an XUnit test from above. All the source code for this series is available on GitHub.

Are you using Azure Cosmos DB? I’d love to hear your experiences so far along. Let me know on twitter or in the comments.


 

9 thoughts on “Optimistic Concurrency in Azure Cosmos DB”

    1. I am attempting to use DocumentDb as the backing store behind an restful API. I need to support optimistic concurrency but am struggling with how to get the ETag back from the client on the POST given a Document’s ETag property is readonly. My current attempt is to have my controller bind to a custom class not inheriting from Document. It contains an ETag property. Is there some trick to get the ETag property set? I don’t see how the generic repo example would work otherwise.

    1. Can you provide actual exception? When you are using the DocumentClient.CreateDocumentQuery, you need to get back a Document and not your type. Meaning you should not be calling DocumentClient.CreateDocumentQuery. If you do that, you will get the document which is then converted to your Type. You can do this, but you then need to do add the ETag property to your type and use the [JsonProperty] attribute. Check out my Customer class as an example how I map the Id.

Leave a Reply

Your email address will not be published. Required fields are marked *