Sunday, March 18, 2012

Context Framework goes to Cloud

A few months ago I wrote a blog post how Context-applications could be clustered. Also the last blog post was about state handling and there I also touched the subject of clustered environments. For short reminder, Context is a stateful web framework and it means that it cannot just be clustered same way as stateless frameworks and in the first post I proposed three different ways to cluster stateful applications.
  1. Session affinity
  2. Node hinting
  3. Node proxying
All these ideas are usable, but I still wasn't happy with it. I wanted to solve the problem: how to share a page scope in a cluster using a database. It should be possible to activate and deactivate cluster nodes by demand, and not to worry whether node has some important information stored into it.

This is now reality in version 0.8.5 which brings native support for clustered environment, and in this blog I want to share what was required to do such effort. It basically meant that two big challenges had to be solved.

1. Page scope must be serializable

There exists a number of different serializing techniques and using them to serialize an object graph is quite simple. That was no brainer. However the challenge was that page components have dependencies to classes that are not serializable. For instances services, or any other "singleton scoped" objects, are such dependencies, and with Guice it is trivial to inject whatever dependencies to components. So this was a big issue.

What I needed was to find a way to detach external dependencies and then retach them back on during deserialization. I chose XStream for the serializer and after some pondering and tweaking its DependencyMapper I was able to do exactly that. I introduced an annotation called @Provided and it marks the dependency be injectable by Guice and can be attached/detached during serialization and deserialization. Also Guice-providers are automatically detached.

For serialization itself I chose binary format. Pure XML-serialization was too large and if XML was compressed, it took some unnecessary processing power. So, binary stream was a good trade off between size and cpu consumption.

2. Page scope access must be synchronized

Other big challenge was synchronization between cluster nodes. The basic thing is that only one server and one thread should be accessing page scope at the same. Concurrent access does not work well, because updates may become mixed and put the scope into inconsistent state.

In reality, the synchronization problem is rare, but it may happen if user is clicking functions in fast pace, thus multiple requests are trying to do something in the page. Of course it has to be remembered that synchronization is needed only within one page, not across different pages.

I chose MongoDB as first persistence implementation and this problem had to be solved in database level.

The basic idea is following. Each mongo-document has a lock-field, which is telling wether document is accessible or not. When page scope is tried to be accessed, the FindAndModify-command is used. Thread looks for document with false in the lock-field and tries to set it true.

If document cannot be accessed thread will sleep for a short period of time (100ms) and try again until access is acquired. In such scenario that there are too many try outs (about 10 seconds), page scope is accessed anyway instead of failing. I figured that this option is better than just some dummy failure.


Other than those challenges, rest was just hard work and refactoring, and so far I'm happy with the results.

Affects on performance


It is obvious that constant serialization/deserialization have impact on performance. So far it seems that the impact is about 10-30ms per request depending on database connection latency and the size of the page scope.

It remains to be seen how much that impact affects overall performance, but Context has on particular advantage over many stateless approaches. To fetch data from server, the amount of needed requests is much lower. Normally just one request is needed to get everything.

For more information check out the documentation for cloudability support.

I am also building a demo-application that I try to keep demonstrating newest functionalities. It is currently hosted at Cloudbees with "Free plan" using 2 node instances.