« Back

Best Practices for Speeding Up Liferay

Company Blogs January 12, 2009 By Eduardo Lundgren Staff

Only 5% of the end-user response time is spent fetching the HTML document. This result holds true for almost all web sites. The most part of websites spend less than 20% of the total response time getting the HTML document. The other 80+% of the time is spent dealing with what's in the HTML document, namely, the front-end. That's why the key to faster web sites is to focus on improving front-end performance. (thanks YAHOO!)

There are three main reasons why front-end performance is the place to start.

  • There is more potential for improvement by focusing on the front-end. Cutting it in half reduces response times by 40% or more, whereas cutting back-end performance in half results in less than a 10% reduction.
  • Front-end improvements typically require less time and resources than back-end projects (redesigning application architecture and code, finding and optimizing critical code paths, adding or modifying hardware, distributing databases, etc.).
  • Front-end performance tuning has been proven to work.


The performance golden rule is: optimize front-end performance first, that's where 80% or more of the end-user response time is spent.

Based on this "golden rules" me and Brian Chan decided (while the New Year's Eve party) to give a New Year's present for Liferay community and start a serie of performance improvements on our front-end.

 

1. Using CSS Sprites

CSS Sprites are the preferred method for reducing the number of image requests. Combine your background images into a single image and use the CSS background-image and background-position properties to display the desired image segment.

We have a new property called theme.images.fast.load. Defaults to true in normal usage and false in development. When the server startups, two files are automatically created on each image folder of your theme ".sprite.png" and ".sprite.properties". These files are only recreated if needed. The taglibs are programmed to know to automatically read the "packed.sprite" and display that relative file on the "packed.png" if this feature is enabled.

packed.png:

 

 
packed.sprite:
arrows/01_down.png=0,16,16
arrows/01_left.png=16,16,16
arrows/01_minus.png=32,16,16
arrows/01_plus.png=48,16,16
...


This will significantly enhance load time (http://css-tricks.com/css-sprites-what-they-are-why-theyre-cool-and-how-to-use-them/).

You can perceive the benefits when you see theme.images.fast.load in action, In a very simple page the number of requests decrease from 59 to 33 and the load time from 885ms to 811ms, in a big page with a lot of contents and hundreds of images it will cause a huge difference.


 ( without using theme.images.fast.load )



  ( using theme.images.fast.load )

 

 2. Cache Filter: Caching Strip Filter and Compress filter

Liferay Portal take advantage of server filters to manipulate headers, strip spaces from the content and even for gzip the content. These filters are doing a good job actually, however, these filters can run us into a problem. Filters are applied on each file on each request, it's very expensive for the server for both processing and memory.

In the old scenario, basically, the first filter applied was the Header Filter that is the most important because it saves the cache on the client-side, after the Strip Filter and the Compress Filter were being applied.


( Old scenario - Represents the priority/order without CacheFilter )
 

Now, in the new scenario, the CacheFilter instead of compressing / stripping per request on static data that has not changed, we know cache the bytes and content encoding and fetch it if the original data has not changed.


( New scenario - Represents the priority with CacheFilter )

This brings significant performance improvements to the server. This filter is cached on the server, it means - all users will take advantage of this technics, for instance, saving 50ms per request, and if the server has an avarage of 10000 request/hour this server will save 500000ms/hour (8.3 minutes/hour) of processing.

The graphic below resumes local tests I did today. Basically, the graphic compares how many milliseconds are spent when the filter are being used and when it's not, for different file sizes requests.

The results are promising, we need the community's help to test and see this improvements in action on live websites on a real enviroment.

 

3. Put Scripts at the Bottom

The problem caused by scripts is that they block parallel downloads. The HTTP/1.1 specification suggests that browsers download no more than two components in parallel per hostname. If you serve your images from multiple hostnames, you can get more than two downloads to occur in parallel. While a script is downloading, however, the browser won't start any other downloads, even on different hostnames.

We recommend if possible you use <footer-portlet-javascript> instead of <header-portlet-javascript> in your liferay-portlet.xml. By this way, we download the unecessary JavaScripts as after as possible.

It's important to remember that Stylesheets should be on top (vide, YAHOO).

 

4. For static components using "Never expire" header

There are two things in this rule:

  • For static components: implement "Never expire" policy by setting far future Expires header
  • For dynamic components: use an appropriate Cache-Control header to help the browser with conditional requests

A first-time visitor to your page may have to make several HTTP requests, but by using the Expires header you make those components cacheable. This avoids unnecessary HTTP requests on subsequent page views. Expires headers are most often used with images, but they should be used on all components including scripts, stylesheets, and Flash components.

Browsers use a cache to reduce the number and size of HTTP requests, making web pages load faster. A web server uses the Expires header in the HTTP response to tell the client how long a component can be cached. Keep in mind, if you use a far future Expires header you have to change the component's filename whenever the component changes. Or concatenate timestamps to the url.

Using a far future Expires header affects page views only after a user has already visited your site. It has no effect on the number of HTTP requests when a user visits your site for the first time and the browser's cache is empty. Therefore the impact of this performance improvement depends on how often users hit your pages with a primed cache. (A "primed cache" already contains all of the components in the page.) The number of page views with a primed cache is 75-85%. By using a far future Expires header, you increase the number of components that are cached by the browser and re-used on subsequent page views without sending a single byte over the user's Internet connection.

 

These are the most important stuffs we worked on these first days of 2009.
I'm sure these are important steps for improving the UI performance and they sounds like prosperation for our product.

Thanks guys!

Threaded Replies Author Date
Very cool! Thanks to you and BChan for... Edward Shin January 12, 2009 2:26 PM
Sweet! Thanks to Eduardo Lundgren and Brian... Jonas Yuan January 12, 2009 4:51 PM
Excellent post Eduardo! David Rison January 13, 2009 6:51 AM
That is very useful!!! Nice post :) Rohit Rai January 20, 2009 1:08 AM
Hi, This is Fred from Taipei. Are these... Fred Wu January 20, 2009 5:32 PM
Or is there a exactly how-to for these 4... Fred Wu January 20, 2009 5:44 PM
--For static components using "Never expire"... gofri _ April 30, 2009 3:56 PM
The strips of images might work ok as an... Lisa Simpson February 28, 2010 2:15 PM
Here is some more analysis for Faster Load... Ankur Srivastava May 4, 2010 7:55 PM
This post is out of date, nowadays we are using... Eduardo Lundgren May 4, 2010 8:05 PM
Great work, thanks for sharing your knowledge Mithun Salinda January 26, 2014 10:20 PM

Very cool! Thanks to you and BChan for implementing these changes!
Posted on 1/12/09 2:26 PM.
Sweet! Thanks to Eduardo Lundgren and Brian Chan ...
Posted on 1/12/09 4:51 PM.
Excellent post Eduardo!
Posted on 1/13/09 6:51 AM.
That is very useful!!! Nice post emoticon
Posted on 1/20/09 1:08 AM.
Hi,

This is Fred from Taipei. Are these features built-in for 4.4.2? or they are for 5.1.x only?
Posted on 1/20/09 5:32 PM.
Or is there a exactly how-to for these 4 aspects? except the theme.css.fast.load=true setting in portal-ext.properties. I've found that setting theme.css.fast.load=true in LEP4.4.2 didn't create the .sprite.png or .sprit.properties by this action itself. I guess we need to do it on our theme design. But now we need to figure out on how to do it by referring to the web article as provided in the link. Is there any anything like step 1-2-3 or wiki already available on this?
Posted on 1/20/09 5:44 PM in reply to Fred Wu.
--For static components using "Never expire" header--
How to set it?
Posted on 4/30/09 3:56 PM.
The strips of images might work ok as an optimization technique if you're actually using all of those images. However, I find that I don't use hardly any of them and the ones I do use are custom. Its wasteful to load 15 images I'm never going to use, particularly for "guest" who isn't going to log in or ever see the control panel. And frankly, many aren't anything that I want to share with an end user as I find it to be something of a security issue. If there's icons in the tab that indicate that there are settings, control panels, etc. it can encourage people to try to access those things. Furthermore, the icons can often give away which system you are using which further encourages hacking attempts.
Posted on 2/28/10 2:15 PM.
Here is some more analysis for Faster Load Times Through Deferred JavaScript Evaluation : http://en.oreilly.com/velocityfall09/public/schedule/detail/11260 Hope it helps
Posted on 5/4/10 7:55 PM.
This post is out of date, nowadays we are using YUI3 and Alloy Loader to manage our lazy-loading and partial Deferred JavaScript Evaluation, so all these tricks are being leveraged.

Thanks anyways.
Posted on 5/4/10 8:05 PM in reply to Ankur Srivastava.
Great work, thanks for sharing your knowledge
Posted on 1/26/14 10:20 PM.