Something that has long plagued web development is a difficulty updating running applications. The typical operation is to cut users off from the application (sometimes brutally), stop the running application, un-deploy the current version, deploy a new version, start the new version, and finally allow users to return to the application. In some cases it’s possible to re-deploy applications in place, but it will usually still result in an interruption to the user as the application is cleaned and restarted.
The Apache Tomcat v7 brings with it a feature called Parallel Deployment, which allows deploying more than one application to the same context. This feature allows any activities going on with a session-based application to continue interacting with the “old” version even as a new version is deployed. This results in an effective zero-downtime solution, as long as your app behaves.
The documentation for Parallel Deployment is pretty brief (http://tomcat.apache.org/tomcat-7.0-doc/config/context.html#Parallel_deployment). It outlines two very important concepts, which I’ll discuss here, but with a little more detail.
There is a hint in the documentation that there’s a way to do parallel deployment by naming the context in the server.xml file. This is soundly defeated by the next comment in the documentation which suggests that the server.xml file is not the place to define contexts. This also defeats our purpose as the server must restart for the server.xml to be re-parsed. Since that’s not our goal for this discussion, we’re going to skip that entirely.
The key to getting Tomcat to recognize a parallel deployment is by identifying the version of the WAR or directory containing the application. Without configuration in the server.xml file, the assumption is that autoDeploy (or deployOnStartup) is enabled for the server. AutoDeploy is on by default in the out-of-the-box server configuration, as this snippet from the server.xml shows:
Note that deployOnStartup is also Tomcat’s default way of operating, but it doesn’t really help the zero-downtime aspect of this, although it does still support Parallel Deployment.
A downside to enabling autoDeploy is that the server will periodically scan the appBase (webapps) for new WAR files and directories. This is a pretty small impact in overall performance, but a necessary evil for Parallel Deployment to function and allow zero downtime.
The second part of this is the naming of the files and directories. Whether deploying the applications as WAR files or exploded directories, the naming works the same. When using Parallel deployment, there’s a tighter relationship to the deployed file and the context.
For many of us, it’s the almost the same relationship we’ve been dealing with for a while, especially when using the autoDeploy feature. That is that the context of the application is the name of the folder or WAR file (sans extension); so if we create a folder named “foo” in our appBase (webapps) folder, or copy a file named “foo.war” into the webapps folder, we will have an application we can reach via “http://www.ourserver.tld/foo” (insert your real URL there). There’s also the special case of a ROOT folder or ROOT.war, which gives us our root context (or http://www.ourserver.tld/ to continue the example.
The addition to naming the deployment file is the ##version of the context. When deploying a WAR file, we name our file with the additional version, such as foo##0.war or in a folder named foo#0. The ##version is compared as a string, so it can be anything that makes sense to you (foo##dev01.war), as long as the names are ascending string comparisons.
Something to watch out for even on case-insensitive systems is name and version collision. Tomcat takes care of collisions by replacing the first deployment with later variations of the same context by changing the case to whatever was there first. That is, if you deploy a version of an application, say foo##A.war, to your server, and then later try to update it with a different version but the same, say foo##a.war, not only is the comparison done with a case-insensitive comparison, but the new deployment will replace the old deployment.
Additionally, even if you change the context but keep the version, to Foo##a.war, it will replace the old deployment. This can be very frustrating if trying to deploy similarly named contexts with the same versioning scheme.
This is only a concern when the potential collision exists. Should you deploy a new context with a different version, you’ll end up with the expected different context and version. That is, if you deploy foo##a.war and Foo##b.war, you’ll end up with two contexts, not an update of the first. For this reason, you might need to put a separator in the version part of the file name, say foo##lower-a.war and Foo#capital-a.war to get the desired separate but similar contexts. Or just be careful to not have similar contexts.
To keep it simple, name your contexts and versions with an insensitive and ascending string comparison in mind. Oh, and try to avoid context and version collisions.
Once the name collisions are avoided, the versioning becomes a simple case of “put a later version with the same context name.” Most of us will likely use some kind of numeric format for our versions. I like to use the date I’m deploying. Something like foo##20120301.war would tell me that I deployed an app on 1 March, 2012; the zero-padding and Y-M-D ensures that the later days in the year come after the earlier days. If I deploy again the next day, I’d change the version to foo##20120302.war. I’ll add the time for those frequently deployed (e.g., development) applications, making the name foo##20120301-1415.war for an app I deployed at 2:15PM on 1 March, 2012. That’s usually enough resolution, but you can get as creative as you need to, just by remembering it’s a string comparison.
There are essentially three ways to deploy applications to Tomcat servers with autoDeploy turned on; copy WAR files, copy directories, and using the Manager application.
Whether copying with WAR files or directories, you need access to the server’s file system. Copy the file or folder to the directory specified by the Host appBase, usually webapps. After a cycle, Tomcat will recognize the new content is there and deploy the application. It’s recommended to archive the application into a WAR file and allow Tomcat to create the directory, exploding what it needs (which will be everything unless unpackWARs is set to false).
When using the Tomcat manager, there are actually two ways to deploy. The easier way is to use the “WAR file to deploy” and select the WAR file, which should be named with the ##version. This will copy the WAR file to the appBase folder and deploy as if you’d copied it there directly.
The other way to deploy using the Tomcat manager requires the archive to exist on the server (or on a resource accessible as a path on the server). Using the “deploy directory or WAR file located on the server” section, as the name suggests, you can select any WAR file or folder that is structured correctly. I still recommend using WAR files. Here, however, the name of the file doesn’t matter, and instead you must put the ##version in the context path, entered as /foo##version in order to be recognized.
Now that we’ve got a handle on getting the applications deployed, it’s important to understand how Tomcat distributes the requests to the applications. With the old way of deploying things, there was always at most one version of an application running in a specific context on a server. With Parallel Deployment, however, can have many versions running at the same time.
Since the objective is zero-downtime deployments of updated applications, we’ll make the assumption that the application is in use. Of course, if an application is not in use, the old manner of remove and reinstall deployment still work, although we can still use Parallel Deployment. A key thing to remember about how Tomcat handles this is that it is tied to the user’s session.
If the user doesn’t have a session, either because they are a new user or because they haven’t done anything in the application to involve a session, they will always be routed to the newest version of an application. Here’s a super-simple application to test this out (and a quick show-off of Servlet 3.0).
Fire up your favorite IDE or text editor (heck, it’s a two-file example, so use the command line all you want!) and create a WEB-INF/web.xml with this simple bit in it:
Revision: A
Start Time: %s
Page Visits: %d
Revision: **B**
Start Time: %s
Page Visits: %d