login - help - about
?
header
<< Back to News

Optimizing page delivery in Web Framework
I had an 'experimental' day today (and about time too). I'm looking at optimizing page delivery. I'm using firebug and google page-speed to diagnose speed issues and have made the following optimizations:
  • Caching of dynamic server js to files
  • Moving away from imports in CSS
  • Collection, concatination, minification and caching of javascript and CSS
  • Adding of gzip filter for dynamic content
If you're interested, read on for the details... and strategies that you might want to apply to alternate html delivery.

Caching of dynamic server js to files
Previously, requests to dynamic service javascript (such as the ForumService) were generated at runtime. Now, they're generated to a file (in the html/svc/ directory) and that file is delivered. Repeated requests deliver from the file. As the services may change, the story is only half told... the undoing is a bit tricky. As the services change (which can happen at runtime), the generated file must be invalidated (deleted). As the web framework is designed to be dynamic, a no-recompile solution is preferred... so, as the cache file is generated, monitors are created for the service source class (or jar, if loaded from a jar) and tied back to the output file. As class files change, events occur which cause the cache file to be deleted. That's not the full story, however. The cache state must be checked on startup and persisted at system shutdown.

This leads to javascript service delivery time falling from 61ms to 30ms for a typical service request. Not a huge benefit, but by caching the files, it can work with concatination and minification.

Moving away from imports in CSS
In the main css (/css/index.css) I included several other CSS files via @import. This was used to keep the code clean with only one link, but I've since found that @import causes issues with pipelining (simultaneous fetching, great link btw). So, the CSS urls were pulled out of index.css and included separately. This enables better pipelining, but more importantly, works better with...

Collection, concatination, minification and caching of javascript and CSS
This one is a bit more involved. For a complex page (the album editor within the ripper) there were several javascript and css files (included with script and links respectively)... like far too many: 18 js files and 12 css files. Each one is a connection that must be established, found, streamed and parsed. Ouch. As I'm including each file dynamically during page creation (building a list of things to include in the page delivery), I figured I could concatinate each file into a large cache file. This file is dynamically created and contains the required content. The name is the hash of the string of the included files in order, so a request for the same group of files results in the same cache file. Now there's a single js and a single css... much nicer for network overhead.

By concatination, the load time (locally) is reduced from 304ms to 71ms for CSS and from 218ms to 62ms for JavaScript. Impressive results.

I then thought it would be a nice time to visit minification and munging. Minification is the replacement of variables and stripping of unnessesary characters, so the output is a small as possible. This reduces the number of bytes sent, speeding transfer and increasing performance. Munging is applied to javascript and further reduces the code size through further substitutions. It will work on single small files, but works best on large files, which is perfect for the concatination.

I'm using the Yahoo YUI Compressor to minify. It does not mess with the javascript/css, doing just minification... and does an excellent job. Because it's not doing anything else, it means that the developer can still create problems (dead code, etc), but it works as a drop-in replacement, which is exactly what I was looking for.

Enabling minification further reduces times from 62ms to 40ms for CSS and from 62ms to 30ms for Javascript. Nice!

I played with the google closure compiler to perform more advanced minification, but it needs code modification on my side to use properly. I might consider it further.

The main downside to minification (and concatination to a certain extent) is that the code is not as it appears locally in the IDE... which makes debugging more difficult. I mitigated this by adding a URL parameter (?clean=true) and a permission. When either is included, the concatination and minification does not occur. I also included a plain-text header in the cache file explaining this, which should help developers to find what they need to do to see the code as they wrote it (and slow things down). This is something that should be done whenever minification is done, IMO. Google is perhaps the worst offender (if only by popularity). The web is built on openness and obfuscation is an affront to this principle. There should be a way to easily source and be sourced. The cache invalidation (as described above) also needs to be done.

Adding of gzip filter for dynamic content

I then added a gzip filter to the jetty embedded servlet engine config. Now all dynamic content is compressed. This is as it should be, and leads to further performance increases. Funnily enough, this increases the load time slightly for CSS from 40ms to 50ms and from 30ms to 85ms for javascript. That could be because the files are quite optimized already, and the compression advantage is outweighed by the time to compress. It does have an effect for larger content tho, such as the transfer of the html, reducing the time from 406ms to 294ms.

All this adds up to alot of work to shave a couple hundred precious milliseconds, but once done it should be transparent to both the viewers and developers. The viewers should see pages faster and the sites (applications) should work better as a whole. The developers should not be inconvenienced by the optimizations and should not have to do anything special to optimize their code. Funky. I still have more optimizations to do, but this is a good start.

These lessons are applicable to sites in general. There is at least one excellent book which explains most of these tips and the google page speed plugin is also excellent in diagnosing issues.

- 07:01 AM, 18 Apr 2011

It really is an improvement... - admin - 07:43 AM, 18 Apr 2011
In casual browsing, the ripper app is much more responsive. Quicker page delivery leading to better experience. I'll roll the code out to here when debugged slightly more. The speedups should be even more impressive over slower networks.

Can't cache service JS - admin - 06:48 PM, 20 Apr 2011
I had to roll back on the caching of service javascript. The JS is different for different users and so can't be cached on the server... atleast, not in its current form.