Zero-downtime Deployment (and Rollback) in Tomcat; a walkthrough and a checklist
If you thought Tomcat could not get any better, you thought wrong. Tomcat 7 introduces what is called Parallel Deployment. This was contributed by SpringSource/VMWare.
Simply put, parallel deployment is the ability to deploy more than one version of your web application in parallel, making all versions available under the exact same URL.
Think about this for a minute. If you have a new version of your application, you can simply drop it into the Tomcat that is running the old one and it will Just Work™. In fact, they will both work. Tomcat handles all session management and traffic routing between application versions. No need to restart Tomcat. No need to stop processing requests. No need to talk to your boss about downtime. No need for your boss to talk to any customers about downtime.
Let’s see this in action, shall we? Using the commands below you can whip up a minimal web application to demonstrate this feature.
$ mkdir WEB-INF $ echo "" > WEB-INF/web.xml $ echo 'old version ' > index.jsp $ jar cf foo##001.war WEB-INF index.jsp $ echo 'NEW version ' > index.jsp $ jar cf foo##002.war WEB-INF index.jsp
You now have two web apps named foo##001.war and foo##002.war. ##001 and ##002 designate the version numbers of the WAR files. Each has their own index page that shows the current time and whether it is the old or the new web application. The people who created this feature chose a surprisingly easy solution for how to tell Tomcat what is an alternate version of what. All you have to do is tack ##<version> onto the WAR’s file name. Simple and effective, if a bit odd-looking.
Now deploy the ’old’ version of the web application.
$ cp foo##001.war apache-tomcat-7.0.12/webapps/
Open a browser, enter the WAR file’s URL (e.g. http://localhost:8080/foo) and watch the time tick by. Note that you do not see the version number on the URL. The page auto-refreshes every second. Beneath the surface, Tomcat will establish a session with your browser. More on that later.
Now deploy the ’new’ version of the web application.
$ cp foo##002.war apache-tomcat-7.0.12/webapps/
Notice how in the already open browser window, the time is still ticking by and it is still showing old version. Open a second browser and in this browser too, open http://localhost:8080/foo. For best results, use an entirely different browser to avoid any session strangeness. I used Safari and Opera for the test.
You should see that the second browser picks up the new web application, while the first browser is still being serviced by the old web application.
Pretty neat, huh?
All-right, so you botched a deployment and want to roll back? Simple, just remove the new version and Tomcat automatically falls back to using the old version. Nice work. Try it now:
$ rm apache-tomcat-7.0.12/webapps/foo##002.war
You will notice the web pages automatically switch to using the old version of the application.
You will have to develop your own deployment strategy. You might choose to let the old versions drain. Once all sessions on the old application have expired, you can remove old deployments from Tomcat. On the other hand, you can just leave the old code deployed. It won’t do any harm.
There are a few things to consider when you want to start using versioned WAR files with your Tomcat server. So before you go off and change the deployment strategy at your company, check off the list below.
- Internal caches should be write-through and expire quickly
- You need sessions to be enabled
- Where does logging go?
- Disk files and directories need to be sharable
- No TCP socket listeners
- Your apps must be able to undeploy
I’ll explain each of these in order. Most are variations on a theme; consider what assumptions your code makes about machine resources.
Internal caches should be write-through and expire quickly
Different version of the same web application each have their own class context. This means that any local caches you have in your web application need to be reviewed. If you cache aggressively and hold on to cached information for a long time, one version of the web application may not see changes that another made.
Picture a situation where both versions of the web application use the same database and both have a local cache to avoid hitting that database. If one version of the web application changes a record in the database, the other won’t see the change until its own cached version of that information has expired.
If you do any in-memory caching in your web application, don’t use parallel deployment until you are sure that the caches will not serve unacceptably stale information.
You need sessions to be enabled
Tomcat uses its own session management to determine what requests should be handled by what version of the web application. If you’ve gone off to implement session handling yourself, or if you switch session handling off in Tomcat, parallel deployment won’t work for you.
Where does logging go?
You probably specified that your logging be written to a log file somewhere. If you do not use the full context name of the application in the log file name, you may end up in a situation where both versions of a web application are writing into the same log file. The problem with that is that you may not know what version of your application generated the output you find in that file.
Disk files and directories need to be sharable
The intention of the designers of Java EE have always intended for web applications to be independent of the underlying machine and file systems. If your application makes use of data files, please take a moment to consider what happens when two versions of your web application start reading and writing them.
In particular, consider that Java’s monitors and locks are confined to a single context. Thus, if you guard access to a file with a lock of some sort, having two versions of that web application means that you have two locks in the JVM, potentially allowing two thread to access it.
No TCP socket listeners
Some applications serve more than just HTTP requests. They have their own TCP socket handlers to serve clients. By deploying more than one version of a web application, you get more than one listener. Obviously this will not work. Only one listener can listen on any given port.
Your apps must be able to undeploy
If you want to be able to roll back botched releases, or if you want to clean out old, unused versions of your web application, your web application needs to undeploy cleanly. Luckily, Tomcat helps you with that too.
I hope these warnings have not put you off using zero-downtime deployment in Tomcat. You may need to solve a few issues for it to work cleanly. But let’s face it, by making your application make fewer assumptions about resources, you get a more robust application anyway.
PS. I like to use SVN revision numbers as a WAR version naming scheme. Thus my WAR files are named foo##<svn revision=””>.war. The only thing to look out for is that versions are compared as strings to determine the version ordering. Thus you may have to zero-pad the version numbers to ensure correct ordering.
Reference: Zero-downtime Deployment (and Rollback) in Tomcat; a walkthrough and a checklist from our JCG partner Kees Jan at the Java Monitor Forum
Related Articles:
I’m in a very interesting situation. I’m validating the migration tomcat 6 to tomcat 7, so we can use the zero donwtime deploy.
One of the problematic situations is our cache. As we’ll have two wars running at the same address int tomcat 7… the cache will be doubled! Cache today is inside servlet context.
The first idea I had was to put the cache within the global JNDI. Thus, all. War can access the same cache in the global JNDI container.
I work, but the problem came …. When I retrieve an object inserted by another war, the JVM generates another internal identifier for the same class in both wars….and so all my cast crashed!!! Especially those inside equals …..That is, within the cache I have an object of class war within thecom.empresa.Cliente ….., I have the same class does not work just right … but the cast!Any idea how to get around this?
What about using some external caching system like memcached or redis?
Dear Fernando,
In response to your question and Damian’s suggestion to use an external cache I wrote a follow-up to this post: http://java-monitor.com/forum/showthread.php?t=4124
Hope you find that useful.
Kees Jan
i tried to deploy ghn-war##001.war but its create folder ghn-web##001 not ghn-web folder i m using tomcat 7 :(