Sunday, July 22. 2012
Couchbase Manager for Glassfish: Asynchronous Operations
Yesterday the version 0.1 of the couchbase-manager was released (only for testing pusposes). I decided to invest some time in order to improve the performance using asynchronous operations. Until now all the operations were synchronous, the manager waited patiently for the couchbase server to return the result. If you think about it the last operation against the memory manager can be done asynchronously, in that way the session is ordered to be saved (or touched) but the operation is just initiated, the control is returned immediately to the manager and the session is processed against the memory repository in the background. Normally the couchbase operation will finish before the next user request reaches the application server. Using background saving sticky configuration has almost none operation against couchbase and non-sticky always saves the last access (actually the operation is done, but in the background).
The modification was quite hard and it made me to add a lot of code to the project. I implemented a totally new way of access the couchbase server but, I think, the new code is much more tidy. A ClientRequest is always created for any couchbase operation and any request produces a ClientResult. Although the request always use the async couchbase/spymemcached method it can be executed synchronously or asynchronously. Synchronization is done just waiting the operation to finish calling the waitForCompletion method in the request. Async execution is always done (just not calling the waiting method) but it is also possible to execute some code after the operation finishes (execOnCompletion method). This method will be used to clean some data in the session once it is saved or touched and it is implemented using a different thread that waits for the operation to finish. Finally there is another method, waitForExecution, this one waits a previous async execution to finish. The waitForCompletion and waitForExecution should not be mistaken. The first one waits the operation to finish, the second waits the thread. The first one is used to perform a synchronous execution, the second one to wait an async execution to finish. The main target for the waitForExecution is waiting when a new operation should be executed but a background one is still being processing (imagine a session is being set in the background but, before terminating, another request access the app server, before being locked it should wait the previous operation to finish).
Besides the async feature, correct exception handling, managing the couchbase errors, has been added to the manager. The main idea is any access to the couchbase server that returns an exception or an incorrect state (for example not found when unlocking) marks the session in ERROR condition and throws an IllegalStateException (a RuntimeException). Of course the error could be retrieved in a background operation, in those cases the session is just marked. Any session in ERROR should be re-read from couchbase in the next request. This last part can be better understood using an sticky example. A new request from a client comes to the app server, as the manager is configured sticky it does not read the session from couchbase and just locked it internally, after the application manages the session it is saved in the background. Background set operation fails. Because of the decoupling, the session can only be marked in error state and the next request is forced to re-read the session from couchbase (no matter if sticky or not, when session is in error state it is always re-read).
As I said async calling is only used in the last part of the request life-cycle (set/cas if the session was modified, touch/unlock if only accessed and unlock/delete if invalidated). Besides if the operation needs two calls (you know some operations needs two couchbase calls, no touchAndUnlock and no unlockAndDelete), only the last one is done in background mode (so non-sticky scenario is again penalized by the two operation issue). Obviously asynchronous processing could also be used in the first locking part but I think in this stage is not worthy (session should be accessed in almost any request, and manager should wait for the operation anyways). In summary, first part of the request (lock in non-sticky configuration) is synchronous and the last part (save, touch or delete in both scenarios, sticky and non-sticky) is asynchronous.
For all that the new features were complicated and I spent some weeks implementing the changes (remember I do all those things in my spare time). I think that now it is completed (and I hope that I have done it correctly). First I show the graphics comparing previous synchronous execution to current async access. I also link the sheet with the meassured times.
Checking the new times I have the following conclusions:
Now sticky configuration is as fast as the non-replicated (normal) glassfish setup. It is only a bit slower in session creation. That is clear cos now no operation against the couchbase server is done in the foreground (stickyness saves the first getAndLock and asynchronous calls save the set/touch final operation). This point I think is incredible.
Non-sticky configuration is still a bit slower than the other setups but, comparing the times between the two non-sticky scenarios, they are very very close, especially in update and refresh operations. As I said before with a more complete set of couchbase lock methods (touchAndUnlock and unlockAndDelete) the times would be a little better but non-sticky configuration always adds some penalty in performance.
There have been several changes since these tests (so maybe the times are different in the 0.1 version) but I wanted to comment the new way of working. You can test the 0.1 version just following the installation instructions on the wiki. Please report any issue or question here in the blog or via github.
An asynchronous goodbye!
Comments