Using optimistic concurrency with RavenDB

Whatever database your are using, relational or document, there is one thing you can be certain of as soon as you get a second user of the system and that is concurrency conflicts. It may not happen often but eventually it will happen that two users load the same document, make some changes to it and save it back to the database.

There are several ways of dealing with these kind of concurrency problems:

  1. Just ignoring it and letting the second users changes overwrite the first ones
    While this isn’t advisable if the data is very important there are lots of cases where it could be an okay solution. Sure it offends the developer in us, as it should, because we are ignoring error situations. But for an end user it might not be that bad at all, specially considering that this is a really cheap option to build and the changes of it occurring are quite low. Just make sure to check with your stakeholder of this is acceptable.
  2. Using pessimistic locking of data
    This option worked quite well in the old days where a client app had a stateful connection to the database. These days that is usually not the case and with web applications that is completely impossible due to the stateless nature of the web. So this one is out.
  3. Using optimistic concurrency
    This basically means we remember the version of a document loaded and pass that along when we save the document. In SQL Server this is typically done using a rowversion. With document database we tend to interact with the database over an HTTP connection and turns out the HTTP protocol has an optimistic concurrency mechanism in the form of E-TAG’s. RavenDB can use these HTTP E-TAG’s to do optimistic concurrency checks for us.

 

Ignoring concurrency issues

This is the easy one and exactly what we get if we use RavenDB as is.

 

Consider the following code:

static void Main(string[] args)


{


    _store = new DocumentStore


    {


        Url = "http://localhost:8081/"


    };


 


    using (_store)


    {


        _store.Initialize();


 


        var id = CreateInitialDocument();


 


        var resetEvent = new ManualResetEvent(false);


 


        var tasks = new List<Task>();


        for (int i = 0; i < 2; i++)


        {


            tasks.Add(Task.Factory.StartNew(() =>


                SimpleUpdateDocument(id, resetEvent)));


        }


 


        Thread.Sleep(1000);


        resetEvent.Set();


        Task.WaitAll(tasks.ToArray());


 


 


        Console.WriteLine();


        Console.WriteLine("The result id:");


        using (var session = _store.OpenSession())


        {


            var theData = session.Load<TheData>(id);


            Console.WriteLine(theData.Data);


        }


    }


}

As you can see the code creates a new document and updates that same document in multiple threads. Then it reads it back in a prints the result.

A simple implementation of updating might look something like this:

private static void SimpleUpdateDocument(string id, ManualResetEvent resetEvent)


{


    using (var session = _store.OpenSession())


    {


        var theData = session.Load<TheData>(id);


        resetEvent.WaitOne();


        theData.Data = string.Format("Updated on thread: '{0}'", Thread.CurrentThread.ManagedThreadId);


        Console.WriteLine(theData.Data);


        session.SaveChanges();


    }


}

Basicaly we load the document, wait for each thread to have the document loaded, update it and save it back to the database. Clearly a concurrency problem arises because the last thread to save the document will try to save its changes and overwrite the others changes.

If we run the code we will see the following output. It appears that there was no error and the changes made in thread 5 where the ones that where ultimately saved.

image

Looking at the RavenDB server window you will see all interactions. Basically two post operations to save the same document right after each other.

image

Maybe this is good enough for your case and then again maybe not.

 

Using optimistic concurrency

Switching to optimistic concurrency is quite easy. All we need to do is set the UseOptimisticConcurrency property on the Advanced object of the session to true. As soon as we do this the second update, the one that would inadvertently overwrite the first one, will result in a HTTP Error 409 – Conflict. This is the result of RavenDB checking for changes using the E-TAG send with each record. If a HTTP Error 409 – Conflict is detected this surfaces in the C# code as a ConcurrencyException. By catching this ConcurrencyException we can decide what to do.

private static void ConcurantUpdateDocument(string id, ManualResetEvent resetEvent)


{


    using (var session = _store.OpenSession())


    {


        try


        {


            session.Advanced.UseOptimisticConcurrency = true;


            var theData = session.Load<TheData>(id);


            resetEvent.WaitOne();


            theData.Data = string.Format("Updated on thread: '{0}'", Thread.CurrentThread.ManagedThreadId);


            Console.WriteLine(theData.Data);


            session.SaveChanges();


        }


        catch (ConcurrencyException ex)


        {


            Console.WriteLine(ex.Message);


            Console.WriteLine("\tExpectedETag: {0}", ex.ExpectedETag);


            Console.WriteLine("\tActualETag:   {0}", ex.ActualETag);


        }


    }


}




using this new version of the update method we will guard against concurrency issues.




If we run the code now we will see the following output:



image



 



And the RavenDB sever will also clearly show the problem.



image



 



Conclusion



Concurrency handling in RavenDB is nice and simple. By default we just get the simple last user wins behavior. This may or may not be appropriate for your situation but is easy as it requires no additional code. If you want concurrency checking all you need to do is set the UseOptimisticConcurrency property on the session to true and that session will be checked.



Nice and simple.



 



Enjoy!

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>