Improving AppFuse's PageSpeed with Apache

One of the most important things when developing webapps is to make them fast. With AppFuse, we've tried to incorporate many of the 14 rules for faster-loading websites. We had a gzip filter before it was cool (2003) and replaced it with the one from EhCache. However, users experienced issues with both of these, both with XFire/CXF and WebWork/Struts 2 and JSPs. Because of these issues, we disabled gzipping a few releases ago.

This article is designed to show you how you can make your AppFuse webapp faster, without modifying any code. The good news is this applies to any webapp that you can deploy behind Apache.

Last Friday, I sent an email to the good folks at Contegix to see if they could install mod_pagespeed on the Apache server that sits in front of *.appfuse.org. My goal was to improve the YSlow and PageSpeed scores of the apps hosted on demo.appfuse.org. I discovered they were getting a dismal score of 24 and figured we could do a lot better. mod_pagespeed speeds up your site and reduces page load time by automatically applying web performance best practices. It seemed like an easy solution.

Unfortunately, we were unable to use mod_pagespeed. From the guys at Contegix:

Attempting to install mod_pagespeed as you requested, we find that it requires Apache httpd 2.2 and libstdc++ 4.1.2, both of which are unsupported in RHEL4. To get mod_pagespeed to work on your present operating system basically means re-rolling the core components, which would make them unsupported. I'm afraid mod_pagespeed is simply not an option on your present configuration.

Since I still wanted to improve performance, I opted for another route instead: using mod_deflate (for gzipping) and mod_expires (for expires headers). I also turned on KeepAlive as recommended by PageSpeed Insights.

mod_deflate
mod_deflate was already installed in Apache (version 2.0.52), so all I had to do was configure it. On RHEL4, Apache is installed at /etc/httpd and there's a conf.d directory that contains all the configuration files. I created a file at /etc/httpd/conf.d/deflate.conf and populated it with the following:

#
# mod_deflate configuration
#
<IfModule mod_deflate.c>
    SetOutputFilter DEFLATE
    
    AddOutputFilterByType DEFLATE text/plain text/html text/xml text/css application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript
    
    DeflateCompressionLevel 9
    
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    
    DeflateFilterNote Input instream
    DeflateFilterNote Output outstream
    DeflateFilterNote Ratio ratio
    
    LogFormat '"%r" %{outstream}n/%{instream}n (%{ratio}n%%)' deflate
    
</IfModule>

At first, I had separate lines for all the different content types (as recommended by this article). The Contegix support crew figured out the solution (everything needed to be on one line) in 14 minutes, updated the config and verified it worked using an http compression testing page.

mod_expires
mod_expires was already installed, so I added a config file at /etc/httpd/conf.d/expires.conf. I used this howto and asked Contegix for help when it didn't work. Their response took quite a bit longer this time (49 minutes), but they once again figured it out:

It appears that FilesMatch does not like to play will JkMount. It does work using content type.

My final config for expires.conf:

<IfModule mod_expires.c>
    ExpiresActive On
    
    <FilesMatch "\.(jpe?g|png|gif|js|css)$">
        ExpiresDefault "access plus 1 week"
    </FilesMatch>
    
    ExpiresByType image/jpeg "access plus 1 week"
    ExpiresByType image/png "access plus 1 week"
    ExpiresByType image/gif "access plus 1 week"
    ExpiresByType text/css "access plus 1 week"
    ExpiresByType application/javascript "access plus 1 week"
    ExpiresByType application/x-javascript "access plus 1 week"
</IfModule>

I used "1 week" because we're changing things quite a bit right now and we haven't integrated resource fingerprinting yet.

KeepAlive
The last thing I did to improve performance was to turn on KeepAlive by editing /etc/httpd/conf/httpd.conf and changing Off to On.

#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On

Summary
As a result of these changes, our PageSpeed score went from 24 to 96 and YSlow went from a 90 to a 98. When I started this experiment, I was only trying to fix demo.appfuse.org. However, it also improved the speed of all the other *.appfuse.org sites, including Confluence, Bamboo, JIRA and FishEye. Thanks for all the help Contegix! There's a good chance you've given me back a few minutes in each day.

Originally posted on the AppFuse Blog.

Posted in Java at Dec 04 2012, 09:25:05 AM MST 7 Comments
Comments:

Can you configure mod_deflate to know which resources can be cached? One of the features of Tapestry that I think is useful is that it caches the gzip'ed streams for static assets, so you only pay the cost once.

Posted by Howard Lewis Ship on December 04, 2012 at 11:36 AM MST #

Howard - I don't believe mod_deflate has any caching features. However, if you have versioned assets (e.g. /assets/css/bootstrap-2.2.1.min.css), you could set a far-future expires header and have it cached by end-users browsers.

Posted by Matt Raible on December 04, 2012 at 09:36 PM MST #

Matt I think the best thing you can do for page load time today is to use pushstate technology like PJAX (https://github.com/defunkt/jquery-pjax) or DJAX (https://github.com/beezee/djax).

Using one of the above libraries requires minimal integration work and maintains your SEO. I'm actually surprised your blog isn't already doing it :)

Posted by Adam Gent on December 05, 2012 at 08:35 AM MST #

Adam - you are correct. We have PJAX on the AppFuse Roadmap and will hopefully add it sometime next year.

As for this blog, I'm planning on adding a mobile versions soon. I'll look into adding PJAX as well. Thanks for the inspiration!

Posted by Matt Raible on December 05, 2012 at 09:17 AM MST #

Some other things that I think would be cool that I don't see on the Roadmap (and I'll look into adding them in AppFuse Jira) are:

  • Flyway instead of LiquidBase
  • Handlebars.java instead of Velocity/JSP etc..

Posted by Adam Gent on December 05, 2012 at 10:30 AM MST #

Let me know if you are interested in instructions for howto with nginx. Cause IMHO apache is great and all (and used by a TON of people) ... but kinda a memory pig and often slow :D

Posted by Christopher Love on December 11, 2012 at 09:28 PM MST #

Chris - I'd definitely be interested in a nginx howto. Bonus points if you add instructions for integrating it with Tomcat or Jetty.

Posted by Matt Raible on December 11, 2012 at 09:29 PM MST #

Post a Comment:
  • HTML Syntax: Allowed