Tomcat Capacity Planning

In this conclusion to a five-part series on tuning Tomcat’s performance, you will learn about the various kinds of capacity planning, and how to do capacity planning for Tomcat. This article is excerpted from chapter four of Tomcat: The Definitive Guide, Second Edition, written by Jason Brittain and Ian F. Darwin (O’Reilly; ISBN: 0596101066). Copyright © 2008 O’Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O’Reilly Media.

Precompiling JSPs at build time using JspC

Here are some valid (as of the time of this writing) reasons for doing build-time precompilation of JSPs:

  1. You need all the performance you can squeeze out of your webapp, and build-time compiled JSPs run faster than JSPs that are compiled inside Tomcat after the webapp is deployed. First, the Java class bytecodes generated in both situations should really be the same, and if they’re not exactly the same, the difference will be very small—certainly not worth a major deployment change such as is necessary to precompile the JSPs before deployment. Also, the time it takes Tomcat to compile the original JSP is usually small and occurs only on the first request of each JSP page after webapp deployment/redeployment. All other requests to the JSP pages serve from the compiled and loaded JSP servlet class (JSPs are compiled into Java servlets). But since JSPs that were compiled before webapp deployment are mapped to the URI space in the web.xml  file, Tomcat is able to route requests to them slightly faster than if the JSP page were compiled at webapp runtime. This is because when JSP pages are compiled during runtime, the resulting servlets must be mapped to the URI space first by the regular URI mapper, which sends the request to the JspServlet, then the request is mapped to the requested JSP page by Tomcat’s JspServlet. Note that the runtime compiled JSPs are mapped via two layers of indirection (two distinct mappers), and precompiled JSPs are mapped via only the first layer of indirection. The performance difference comes down to the performance of the two different URI mapper situations. In the end, precompiled JSPs usually run about 4 percent faster. Precompiling them before webapp deployment would save you the small initial request compile time for each JSP page in your webapp, plus the 4 percent performance improvement on each subsequent request for a JSP page. In Tomcat 4.1.x, the runtime JSP request mapper was noticeably slower than the web.xml  servlet mapper and made it worth precompiling JSPs before webapp deployment. That made JSP pages faster by approximately 12 percent or so in our tests. But, for Tomcat version 5.0.x and higher, this margin was reduced to about 4 percent or less.
  2. By precompiling JSPs at webapp build or packaging time, the syntax for the JSPs is checked during the JSP compilation process, which means that you can be confident that the JSPs at least compile with no syntax errors before you deploy your webapp. This is great a way to avoid the situation where you have deployed your webapp to your production
    server(s) only to find out later that one of the JSPs had a syntax error, and it was found by the first user who requested that page. Also, finding errors in the development phase of the code allows the developer to find and fix the errors more rapidly; it shortens the development cycle. This will not prevent every kind of bug because a compiled JSP may still have runtime logic bugs, but at least you can catch all syntax errors in the development environment.
  3. If you have a large number of JSP files in your webapp, each of which is somewhat long (hopefully you are not copying and pasting lots of content from one JSP page to many other JSP pages; you should instead make use of the JSP include feature), the initial compilation time for all the JSP pages combined could be significantly large. If so, you can save time on the production server by precompiling the JSPs before webapp deployment time. This is especially helpful if your traffic load is high, and your server responses would otherwise slow down quite a bit, while the server is initially compiling many JSP pages at the same time when the webapp is first started.
  4. If you have a low server resource situation, for instance, if the Java VM is configured to use a small amount of RAM or the server does not have very many CPU cycles for Tomcat to use, you may not want to do any JSP compilation at all on the server. Instead, you could do the compilation in your development environment and deploy only compiled servlets, which would lighten the utilization of both memory and CPU time for the first request of each JSP file after each new copy of the webapp is deployed.
  5. You are developing a JSP web application that you will sell to customer(s) whom you do not want to have the JSP source code. If you could give the customer(s) the webapp containing just compiled servlets, you could develop the webapp using the original JSPs, and ship it with the compiled JSP servlets. In this use case, precompiling before release to the customer is used as a source code obfuscation mechanism. Keep in mind, though, that compiled Java class files are relatively easy to decompile into readable Java source code, but (as of this writing) there is no way to decompile it all the way back into JSP source code.
  6. Also, as of Tomcat version 5.5, you no longer need a JDK that has a built-in Java source compiler to serve runtime compiled JSPs. Tomcat versions 5.5 and higher come bundled with the Eclipse JDT compiler, which is a Java compiler that is itself written in pure Java. Because the JDT compiler is bundled as part of Tomcat, Tomcat can always compile JSPs into servlets, even when Tomcat is run on a JRE and not a JDK.

Example 4-4 is an Ant build file that you can use to compile your webapp’s JSP files at build time.

Example 4-4. The precompile-jsps.xml Ant build file

<project name="pre-compile-jsps" default="compile-jsp-servlets">

  <!– Private properties. — >
  <property name="webapp.dir" value="${basedir}/webapp-dir"/>
  <property name="tomcat.home" value="/opt/tomcat"/>
  <property name="jspc.pkg.prefix" value="com.mycompany"/>
  <property name="jspc.dir.prefix" value="com/mycompany"/>

  <!– Compilation properties. –>
  <property name="debug" value="on"/>
  <property name="debuglevel" value="lines,vars,source"/>
  <property name="deprecation" value="on"/>
  <property name="encoding" value="ISO-8859-1"/>
  <property name="optimize" value="off"/>
  <property name="build.compiler" value="modern"/>
  <property name="source.version" value="1.5"/>

  <!– Initialize Paths. –>
  <path id="jspc.classpath">
    <fileset dir="${tomcat.home}/bin">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${tomcat.home}/server/lib">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${tomcat.home}/common/i18n">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${tomcat.home}/common/lib">
      <include name="*.jar"/>
    </fileset>
    <fileset dir="${webapp.dir}/WEB-INF">
      <include name="lib/*.jar"/>
    </fileset>
    <pathelement location="${webapp.dir}/WEB-INF/classes"/>
    <pathelement location="${ant.home}/lib/ant.jar"/>
    <pathelement location="${java.home}/../lib/tools.jar"/>
  </path>
  <property name="jspc.classpath" refid="jspc.classpath"/>

  <!–========================================================== –>
  <!– Generates Java source and a web.xml file from JSP files.                     –>
  <!– ==========================================================
–>
  <target name="generate-jsp-java-src">

    <mkdir dir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"/>
    <taskdef classname="org.apache.jasper.JspC" name="jasper2">
      <classpath>
        <path refid="jspc.classpath"/>
     
</classpath>
    </taskdef>
    <touch file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
    <jasper2 uriroot="${webapp.dir}"
             package="${jspc.pkg.prefix}" 
          webXmlFragment="${webapp.dir}/WEB-INF/jspc-web.xml"
             outputDir="${webapp.dir}/WEB-INF/jspc-src/${jspc.dir.prefix}"
             verbose="1"/>
 
</target>

  <!– ========================================================== –>
  <!– Compiles (generates Java class files from) the JSP servlet –>
  <!– source code that was generated by the JspC task.            –>
  <!– ========================================================== –>
  <target name="compile-jsp-servlets" depends="generate-jsp-java-src">
    <mkdir dir="${webapp.dir}/WEB-INF/classes"/>
   
<javac srcdir="${webapp.dir}/WEB-INF/jspc-src"
          
destdir="${webapp.dir}/WEB-INF/classes"
          
includes="**/*.java"
          
debug="${debug}"
          
debuglevel="${debuglevel}"
          
deprecation="${deprecation}"
          
encoding="${encoding}"
          
optimize="${optimize}"
          
source="${source.version}">
     
<classpath>
        <path refid="jspc.classpath"/>
      </classpath>
    </javac>
  </target>

  <!– ========================================================= –>
  <!– Cleans any pre-compiled JSP source, classes, jspc-web.xml –>
  <!– ========================================================= –>
  <target name="clean">
    <delete dir="${webapp.dir}/WEB-INF/jspc-src"/>
   
<delete dir="${webapp.dir}/WEB-INF/classes/${jspc.dir.prefix}"/>
   
<delete file="${webapp.dir}/WEB-INF/jspc-web.xml"/>
 
</target>

</project

If you put this Ant build xml content into a file named something such as pre compile-jsps.xml , you can test it alongside any build.xml  file you already have, and if you like it, you can merge it into your build.xml .

This build file will find all of your webapp’s JSP files, compile them into servlet classes, and generate servlet mappings for those JSP servlet classes. The servlet map pings it generates must go into your webapp’s WEB-INF/web.xml file, but it would be difficult to write an Ant build file that knows how to insert the servlet mappings into your web.xml  file in a repeatable way every time the build file runs. Instead, we used an XML entity include so that the generated servlet mappings go into a new file every time the build file runs and that servlet mappings file can be inserted into your web.xml file via the XML entity include mechanism. To use it, your webapp’s WEB- INF/web.xml must have a special entity declaration at the top of the file, plus a reference to the entity in the content of the web.xml  file where you want the servlet mappings file to be included. Here is how an empty servlet 2.5 webapp’s web.xml  file looks with these modifications:

  <!DOCTYPE jspc-webxml [
    <!ENTITY jspc-webxml SYSTEM "jspc-web.xml">
 
]>

  <web-app xmlns=http://java.sun.com/xml/ns/javaee
      xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance
      xsi:schemaLocation="http:// java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/
 
javaee/web-app_2_5.xsd"
      version="2.5">

    <!– We include the JspC-generated mappings here. –>
    
&jspc-webxml;

    <!– Non-generated web.xml content goes here. –>

  </web-app>

Make sure your webapp’s web.xml file has the inline DTD (the DOCTYPE tag) all the way at the top of the file and the servlet 2.5 web-app schema declaration below that. Then, wherever you want to insert the generated servlet mappings in your web.xml   file, put the entity reference &jspc-webxml; . Remember, the entity reference begins with an ampersand ( & ), then has the name of the entity, and ends with a semicolon ( ; ).

To use the build file, just edit it and set all of the properties at the top to values that match your setup, and then run it like this:

  $ ant -f pre-compile-jsps.xml 
  Buildfile: pre-compile-jsps.xml

  generate-jsp-java-src:
    [jasper2] Sep 27, 2008 10:47:15 PM org.apache.jasper.xmlparser.MyEntityResolver
  resolveEntity
    [jasper2] SEVERE: Invalid PUBLIC ID: null
    [jasper2] Sep 27, 2007 10:47:17 PM org.apache.jasper.JspC processFile
    [jasper2] INFO: Built File: /index.jsp

  compile-jsp-servlets:
      [javac] Compiling 1 source file to /home/jasonb/myproject/webapp-dir/WEB-INF/
  classes

  BUILD SUCCESSFUL
  Total time: 7 seconds

Any JSP files you have in your webapp dir will be compiled into servlets, and when you deploy the webapp, the JSP page requests will be mapped to the compiled serv lets. Ignore the “ SEVERE: Invalid PUBLIC ID: null ” message if you get it; it’s bogus. If you want to clean out the compiled servlets and their generated Java source and mappings, just execute the clean target like this:

  $ ant -f pre-compile-jsps.xml clean

One thing that this build file does not do: remove all of the JSP files in your webapp after compiling them. We didn’t want you to accidentally delete your JSP files, so we intentionally left it out. Your own build file should do that before the webapp gets deployed. If you forget and accidentally leave the JSP files in the deployed webapp, none of them should get served by Tomcat because the web.xml  file explicitly tells Tomcat to use the compiled servlet classes instead.

{mospagebreak title=Capacity Planning}

Capacity planning is another important part of tuning the performance of your Tomcat server in production. Regardless of how much configuration file-tuning and testing you do, it won’t really help if you don’t have the hardware and bandwidth your site needs to serve the volume of traffic you are expecting.

Here’s a loose definition of capacity planning as it fits into the context of this section: capacity planning is the activity of estimating the necessary computer hardware, operating system, and bandwidth necessary for a web site by studying and/or estimating the total network traffic a site will have to handle, deciding on acceptable service characteristics, and finding appropriate hardware and operating systems that meet or exceed the server software’s requirements to meet the service requirements. In this case, the server software includes Tomcat, as well as any third-party web servers and load balancers that you are using “in front” of Tomcat.

If you don’t do any capacity planning before you buy and deploy your production servers, you won’t know if the server hardware can handle your web site’s traffic load. Or, worse still, you won’t realize the error until you’ve already ordered, paid for, and deployed applications on the hardware—usually too late to change direction very much. You can usually add a larger hard drive or even order more server computers, but sometimes it’s less expensive overall to buy and/or maintain fewer server computers in the first place.

The higher the volume of traffic on your web site, or the larger the load that is generated per client request, the more important capacity planning becomes. Some sites get so much traffic that only a cluster of server computers can handle it all within reasonable response time limits. Conversely, sites with less traffic have less of a problem finding hardware that meets all their requirements. It’s true that throwing more or bigger hardware at the problem usually fixes things, but, especially in the high traffic cases, that may be prohibitively costly. For most companies, the lower the hardware costs are (including ongoing maintenance costs after the initial purchase), the higher profits can be. Another factor to consider is employee productivity. If having faster hardware would make the developers 20 percent more effective in getting their work done quickly, for example, then depending on the size of the team, it may be worth the hardware cost difference to order bigger/faster hardware up front.

Capacity planning is usually done at upgrade points as well. Before ordering replacement hardware for existing mission-critical server computers, it’s probably a good idea to gather information about what your company needs, based on updated requirements, common traffic load, software footprints, etc.

There are at least a couple of common methods of arriving at decisions when conducted capacity planning. In practice, we’ve seen two main types: anecdotal approaches and academic approaches, such as enterprise capacity planning.

Anecdotal Capacity Planning

Anecdotal capacity planning is a sort of light capacity planning that isn’t meant to be exact, but close enough to keep a company out of situations that would be caused by doing no capacity planning at all. This method follows capacity and performance trends that are obtained from previous industry experience. For example, you could make your best educated guess at how much outgoing network traffic your site will have at its peak usage (hopefully from some other real-world site), and double that figure. That figure is your site’s new outgoing bandwidth requirement for which you will make sure to buy and deploy hardware that can handle it. Most people will do capacity planning this way because it’s quick and requires little effort and time.

Enterprise Capacity Planning

Enterprise capacity planning is meant to be more exact and takes much longer. This method is necessary for sites with a very high volume of traffic, often combined with a high load per request. Detailed capacity planning like this is necessary to keep hardware and bandwidth costs as low as they can be, while still providing the quality of service that the company guarantees or is contractually obligated to live up to. Usually, this involves the use of commercial capacity planning analysis software in addition to iterative testing and modeling. Few companies do this kind of capacity planning, but the few that do are very large enterprises that have a budget large enough to afford doing it (mainly because this sort of thorough planning ends up paying for itself).

The biggest difference between anecdotal and enterprise capacity planning is depth. Anecdotal capacity planning is governed by rules of thumb and is more of an educated guess, whereas enterprise capacity planning is an in-depth requirements-and-performance study whose goal is to arrive at numbers that are as exact as possible.

{mospagebreak title=Capacity Planning on Tomcat}

To capacity plan for server machines that run Tomcat, you could study and plan for any of the following items (this isn’t meant to be a comprehensive list, but instead a list of some common items):

Server computer hardware

Which computer architecture(s)? How many computers will your site need? One big one? Many smaller ones? How many CPUs per computer? How much RAM? How much hard drive space and what speed I/O? What will the ongoing maintenance be like? How does switching to different JVM implementations affect the hardware requirements?

Network bandwidth

How much incoming and outgoing bandwidth will be needed at peak times? How might the web application be modified to lower these requirements?

Server operating system

Which operating system works best for the job of serving your site? Which JVM implementations are available for each operating system, and how well does each one take advantage of the operating system? For example, does the JVM support native multithreading? Symmetric multiprocessing (SMP)? If SMP is supported by the JVM, should you consider multiprocessor server computer hardware? Which serves your webapp faster, more reliably, and less expensively: multiple single-processor server computers or a single four-CPU server computer?

Here’s a general procedure for all types of capacity planning, and one that is particularly applicable to Tomcat:

  1. Characterize the workload. If your site is already up and running, you can measure the requests per second, summarize the different kinds of possible requests, and measure the resource utilization per request type. If your site isn’t running yet, you can make some educated guesses at the request volume and run staging tests to determine the resource requirements.
  2. Analyze performance trends. You need to know what requests generate the most load and how other requests are in comparison. Knowing which requests generate the most load or use the most resources, will help you know what to optimize to have the best overall impact on your server computers. For example, if a servlet that queries a database takes too long to send its response, maybe caching some of the data in RAM would safely improve response time.
  3. Decide on minimum acceptable service requirements. For example, you may not want the end user to ever wait longer than 20 seconds for a web page response. That means that even during peak load, no request’s total time from the initial request to the completion of the response can take longer than 20 seconds. That may include any and all database queries and filesystem access needed to complete the heaviest resource-intensive request in your application. The minimum acceptable service requirements are up to each company and vary from company to company. Other kinds of service minimums include the number of requests per second the site must be able to serve and the minimum number of concurrent sessions and users.
  4. Decide what infrastructure resources you will use, and test it in a staging environment. Infrastructure resources include computer hardware, bandwidth circuits, operating system software, and so on. Order, deploy, and test at least one server machine that mirrors what you’ll have for production and see if it meets your requirements. While testing Tomcat, make sure you try more than one JVM implementation, try different memory size settings, and request thread pool sizes (discussed earlier in this chapter).
  5. If step 4 meets your service requirements, you can order and deploy more of the same thing to use as your production server computers. Otherwise, redo step 4 until service requirements are met.

Be sure to document your work because it tends to be a time-consuming process that must be repeated if someone needs to know how your company arrived at the answers. Also, because the testing is an iterative process, it’s important to document all of the test results on each iteration and the configuration settings that produced the results so you know when your tuning is no longer yielding noticeable positive results.

Once you’ve finished with your capacity planning, your site will be much better tuned for performance, mainly due to the rigorous testing of a variety of options. You should have gained a noticeable amount of performance just by having the right hardware, operating system, and JVM combination for your particular use of Tomcat.

{mospagebreak title=Additional Resources}

As mentioned in the introduction to this section, one chapter is hardly enough when it comes to detailing performance tuning. You would do well to perform some additional research, investigating tuning of Java applications, tuning operating systems, how capacity planning works across multiple servers and applications, and anything else that is relevant to your particular application. To get you started, we wanted to provide some resources that have helped us.

Java Performance Tuning by Jack Shirazi (O’Reilly) covers all aspects of tuning Java applications, including good material on JVM performance. It is a great book that includes information about developer-level performance issues in great depth. Of course, Tomcat is a Java application, so much of what Jack says applies to your instance(s) of Tomcat. As you learned earlier in this chapter, several performance enhancements can be achieved just by editing Tomcat’s configuration files.

Keep in mind that while Tomcat is open source, it’s also a very complex application, and you might want to be cautious before you start making changes to the source code. Use the Tomcat mailing lists to bounce your ideas around, and get involved with the community if you decide to delve into the Tomcat source code.

If you’re running a web site with so much traffic that one server may not be enough to handle the whole load, you should probably read Chapter 10—which discusses running a web site on more than one Tomcat instance at a time, potentially on more than one server computer.

You can find more web pages on capacity planning simply by searching for “capacity planning” on the Net. A couple of good examples are http://en.wikipedia.org/wiki/ Capacity_planning and http://www.informit.com/articles/article.asp?p=27641&rl=1 .


* There is also the server-side approach, such as running Tomcat under a Java profiler to optimize its code, but this is more likely to be interesting to developers than to administrators.

* Siege is not able to test with keep-alive connections turned on—a feature that siege is missing, at least as of this writing. This means that using siege, you cannot perform the highest performance benchmark testing, although siege also implements other types of testing that ab does not implement, such as regression testing
and an “” mode, where it can generate randomized client requests to more closely simulate real web traffic.

[gp-comments width="770" linklove="off" ]

chat