Tuesday, March 24, 2020

Explaining Optimistic concurrency in Azure Cosmos DB with REST API



     Optimistic Concurrency Control is important for many applications and databases. Let me try to explain what it is without using any fancy technical words. Let’s say you are creating a web application and you have an update page which lets your users to update some type of model in your application. You have many users and this page can be open by many users in the same time. That means many users might try to update the same model without seeing each other’s data. Which update are you going to trust in this situation? Also, your users might update something without seeing the latest version of model.

     To handle this problem, usually developers use a column like LastUpdateDt. You bring this column to Frontend and post it back to database with updated model. If LastUpdateDt hasn’t changed updates goes into database. If LastUpdateDt is changed, that means somebody else updated this model and your code rejects changes.

     In this post, I will try to answer questions likeHow do we do this in Azure Cosmos DB?  Do I need to do all that logic manually by using Cosmos DB SDK? I will use Cosmos DB's REST API to demo how Cosmos DB handles Optimistic Concurrency Control automatically. If you have experience developing any REST API, you might be familiar with headers like If-Match or If-None-Match. These HTTP headers controls what should be updated or not. Also, you can use them for caching too since they check which item is updated or not, you may need to cache items until they are changed.

     Unlike many Database Engines Azure Cosmos DB has a REST API which can be used to do any operation in the database. Every resource in Cosmos DB has some internal properties. One of those properties is the Entity Tag which is represented by name _etag. Here is an example of one document.



 Cosmos DB REST API supports the following HTTP Request headers to control Optimistic Concurrency Control of your application. You can make any database operation conditional by using them.

If-Match
Requested Operation will happen if passed Entity Tag matches to database’s version.

If-None-Match
Requested Operation will happen if passed Entity Tag does not match database’s version.

   Talk is cheap, let me show you this in action. I will use PostMan to demo this. You can try to use PostWoman too by the way. You can read my earlier Post about what you need to know to send a request to Azure Cosmos DB by using Postman.

    First, one of our users requests to read a document by following request.


     This is a READ operation, that's why we use GET Http Verb for it. I am using Azure Cosmos DB Emulator as source, that's why I have localhost in the url. Pre-Request Script has the code which generates the authorization and x-ms-date values. To make this operation work, you need to pass the partition key by using x-ms-documentdb-partitionkey header. Value must be an array and data type matters. If my partition key was string, the value must be ["905"]... I learned that in hard way :) Also, in documentation it says partition key is optional... Currently, you cannot create any collection without specifying a partition key.  In my opinion, partition key is required not optional.... Here is what PreRequest Script tab looks like.



   This script generates the authentication token for the READ operation. When I click on SEND button, I get the following result. Pay attention to the Entity Tag property (_etag)



    So far, user read this document and front-end is showing this data in Update form. Let's say we have another user already has this document on her/his page and clicks on Update. To simulate that, I am going to update CreditLimit to 1000 manually from the Emulator. Here what data looks like after my manual update.


   As you can see, etag and _ts is changed with my manual update. Now, we have a problem. Our user thinks that CreditLimit is still 2500. In this situation, I want my database to not accept user's update operation. I will use POSTMAN to demo the Update statement.


     Since I am doing an UPDATE operation, I changed HTTP Verb to PUT. I added If-Match header and passed the etag value I have. As you can see this version does not match to current value. I passed the data to the Body of HTTP Request. It looks like this user is trying update CreditLimit to 1500.


     When I click Send button, I see Precondition error from Azure Cosmos DB.


    As you can see, Cosmos DB rejects this update thanks to If-Match HTTP Header. In this situation, you can warn your user and serve the latest data with new entity tag to user. If user still wants to update it, he or she can send another update operation with new etag. If etag will match, update will work.

     You might say, this is great, but I use SDK not REST API. Don't worry, you can do this from SDK too. You need to use AccessCondition class to control If-Match header. He is an example from Cosmos DB website.

await client.ReplaceDocumentAsync(
    readCopyOfBook.SelfLink, 
    new Book { Title = "Moby Dick", Price = 14.99 },
    new RequestOptions 
    { 
        AccessCondition = new AccessCondition 
        { 
            Condition = readCopyOfBook.ETag, 
            Type = AccessConditionType.IfMatch 
        } 
     });


No comments:

Post a Comment