Monday, August 22, 2011

Clustering Context-application in Cloudbees

Scalability and clustering is normally associated with stateless web applications. Because Context is stateful, I began to experiment, what is needed to serve Context-application from the cloud. I was introduced to Cloudbees, which offers 2 nodes in their free subscription plan.

First thing is important to understand that, in Context, state does not rely on cookies thus it does not rely on http-session. Instead it relies on UUID that is created for each page load and all page update use that token to identify which page should receive the update request.

This means that, from the clustering point of view, all page loads can always go to any random node, but all subsequent ajax-calls on each page must go to the original node. Resource-requests (images, js-files, css-file etc.) can always go to any random node

So, there are at least three ways to handle clustering that suits Context-applications.

1. Session affinity

This is the most basic way to handle the situation. With session affinity all page loads are directed to same node for the same user. It will work, but is not the most "pure" option. The good thing is that session affinity is a known technique and it has support, although Cloudbees does not offer it yet. So, if session affinity is offered everything should be set.

2. Node hinting

This is the way I'd like clustering to happen. In this version each node exposes some kind of token that is sent to web client and if the client sends the same token back during request, then the load balancer will automatically send the request to the originating node.

This would allow each page load request to go to any random node, but all ajax-request would go to the originating node. Unfortunately, in my understanding, this option is not supported.

3. Node proxying

Because the options above were not supported in Cloudbees, I thought something different. Simply, could nodes proxy each other?

Basically this scheme is the same as option 2, but with a twist. When page load is requested, the local IP-address and TCP-port of the node is sent to web client (with hash). And that is sent back to the cloud during ajax-request.

Then the receiving node will check if the ip-address and port belongs to it. If so, call is served. If not, the node will proxy the request to correct node using the local ip-address and port. This was a technique that actually works in Cloudbees.

There is a live demo at Cloubees that demonstrates the behaviour. I had to make a custom http-proxy-filter and if you are interested the example is found here. It is not perfect, but at least shows the idea.

Now, these options has some challenges though. One partical challenge is that depending on traffic new nodes can be brought alive or shutdown. It is always ok to start new nodes, but for shutting them down may be problematic. If the node still contains some users, their pages will suddenly stop from working. So, it basically means that the number of nodes should stay the same.

New features in 0.8.4

Also, new version has been released. Here are the prominent changes.

Extended live class reloading

Version 0.8.3 brought live class reloading to pages and components with limitations. Those limitiations were that creation of new pages required server restart, so that their mappings would be registered. Version 0.8.4 removes that restriction and in development mode new pages (or removals) are discovered at runtime.

This change means that for most of the time no server restarts are needed. Complete documentation is here.

Path- and Request-param-mapping

This version also brings support for mapping path- and request-parameters as view component properties or as methods. This happens with two annotations @PathParam and @RequestParam.

Example:
http://www.example.com/myview/10?search=contextfw

@PageScoped 
@View(url="/myview/<id>")
public class MyView extends Component implements ViewComponent {

  @PathParam
  private Long id;

  @RequestParam
  private String search;

  @Override
  public void initialize(ViewContext context) {
    System.out.println(id + ":" + search); // => prints 10:contextfw
  }
}
Complete documentation is here.

JSONP-embedded mode with RequestInvocationFilter

RequestInvocationFilter is more esoteric and advanced feature and is not needed in everyday use. But in essence it is way to create filtering to page initialization and updates in similar manner as regular servlet filter. The difference is that this filter does not touch resources as images or css-files.

The filtering was implemented to enhance embedded mode usage. In normal embedded mode same origin policy is applied, but modifying contextfw.js-file and applying invocation filter, it is possible to embed pages in JSONP-mode thus pages can be embedded everywhere.

At this moment JSONP-mode is experimental, but if you are interested in it, I'm happy to share my experiments so far.

Documentation about embedded mode is here.