Improving Java web site performance with asset caching 9

Posted by Matt Parrish Thu, 01 May 2008 23:04:00 GMT

In this post, I’ll be talking about a solution I developed at my day job to improve the performance of our web site by allowing the browser to cache JavaScript, CSS, and image files. We were noticing that much of our traffic was from requests for these assets, rather than our page content. Since these asset files rarely change (once per production deployment), we wanted to have the user’s browser cache them until the next build.

We use Yahoo’s YSlow Firefox plugin to analyze the performance characteristics of our site and we were getting bad grades for the following category: Add an Expires or a Cache-Control Header. Images are easy enough to cache. Just add an HTTP response header for the images to have them expire 10 years in the future. If you ever need to change an image, instead of modifying it, just create a new one with a different URL. To set this exipres header, I created the following class, called StaticFileFilter:

package org.pearware.web.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Matt Parrish
 */
public class StaticFileFilter implements Filter {

    public void destroy() {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        long expires = 365*24*60*60*1000;
        httpResponse.setDateHeader("Expires", System.currentTimeMillis() + expires);
        httpResponse.setHeader("Cache-Control", "max-age=" + expires);
    }

    public void init(FilterConfig config) throws ServletException {
    }

}

In web.xml, we add this filter for all images:

  
    StaticFileFilter
    org.pearware.web.filter.StaticFileFilter
  

  
    StaticFileFilter
    *.png
  

  
    StaticFileFilter
    *.jpg
  

  
    StaticFileFilter
    *.gif
  

So, now we have our image files cached for 10 years. JavaScript and CSS files, however, tend to change frequently, possibly with every new build. Unlike with images, it really becomes a pain to rename the files, especially when using Source Control and you want to track the changes of the files over time. My solution was to have the build number become part of the URL for these assets, without having to change the names or locations of the files. The StaticFileFilter then becomes responsible for translating the build-dependent URL into the location for the actual resource. Here’s the new body of the StaticFileFilter.

        String buildNumber = SomeWayTo.getBuildNumber();
        String oldRequestURI = httpRequest.getRequestURI();
        String requestURI = oldRequestURI.replaceFirst(buildNumber + "/(js|css|images)", "$1");
        long expires = 365*24*60*60*1000;
        httpResponse.setDateHeader("Expires", System.currentTimeMillis() + expires);
        httpResponse.setHeader("Cache-Control", "max-age=" + expires);
        request.getRequestDispatcher(requestURI).forward(request, response);

What this does is change the requested URI from, say, /2.3/images/sample.jpg to /images/sample.jpg. The last line of code is responsible for requesting the image at the actual path. The only magic here is how to get the build number. For us, we update a properties file with the build number before every QA build and have a class that reads in the property.

The other piece to this is that our XHTML page needs to reference images as <img src=”/2.3/images/sample.jpg”/> instead of <img src=”/images/sample.jpg”/>. Since the build number changes with each deployment, we need to dynamically generate this build number. Here’s how I’m doing it, using Freemarker:

[#macro css href]

[/#macro]

[#macro script src]

[/#macro]

[#macro img src params…]
[#compress]

[/#compress]
[/#macro]

[@script src=”js/prototype”/]
[@css href=”css/main”/]
[@img src=”images/logo.png” alt=”Logo”/]

What you can see is that the StaticFileFilter is setup to serve all of our CSS, JavaScript and images. The benefit is that we are now free to modify any of our assets, yet still instruct the browser to cache them for 10 years. When we push out a new build, the browser will see new URL’s for the assets due to the new build number. Our deployment doesn’t change; we don’t need to move files around to support this. It’s been working great for us. Our YSlow score has gone up and our HTTP traffic for these files has decreased dramatically, freeing up our server and network for serving up the actual content of our application

ActiveMailer email performance on site5

Posted by Matt Parrish Thu, 09 Nov 2006 19:52:00 GMT

The site I am working on, RealIdaho.com, is hosted by site5. There are some forms that, when filled out by the user, send confirmation emails to the user, to us internally, and to the realtor’s cellphone. We started getting some reports that the confirmation page wouldn’t come up, and users were resubmitting the form many times. I discovered a few things while investigating that may be useful to others.

The first thing I noticed, is that the page would timeout after 15 seconds and just return a blank page. Not very friendly, and definitely explains why the users were unsure that the form was submitted properly. The site however continued to process the request, and eventually got all the emails out. I contacted the site5 support team and asked the 1) why is the page timing out after only 15 seconds, and 2) why was sending email so slow.

Here’s the answer I got for item 1:

“Changing the timeout would affect the global settings for apache. This results in a significant increase in the number of open connections that apache has and degrades server performance. I can ask about this but I don’t think it will be possible to increase the timeout.”

While I understand their reasoning, 15 seconds seems awfully short. I believe the default for apache is 5 minutes. Hopefully, they’ll consider increasing that amount at least to 30-60 seconds. While I never want a page that takes more than 5 seconds, I’m sure we’ll have some pages that do take longer, especially ones that need to communicate with external systems.

Regarding number 2, the support technician suggested using sendmail directly instead of sending email over SMTP, as that should be much faster. So, I went into RAILS_ROOT/config/environments/production.rb and added the following line:

ActiveMailer::Base.delivery_method = :sendmail

Here are the performance numbers:

Previous method (SMTP):

Sending Mail to user (25.22760)
Sending Mail to internal (31.11207)
Sending Mail notification (20.70688)

Current method (Sendmail):

Sending Mail to user (0.16841)
Sending Mail to internal (0.28479)
Sending Mail notification (0.24701)

Wow, that’s quite a difference! When I tested the form submission, the confirmation page appeared almost immediately. Now that’s more like it. Now I just need to see about increasing the timeout.