Rating
+6.93
Votes:
0
avatar

Web Performance Optimization  

Stylesheet Composition

Browsers cannot start rendering till all Stylesheets loaded. Visitors see blank page (or previous page) during loading of CSS so it’s especially important to load these components faster.
If you have many Stylesheet components, then visitors will have to wait more because of HTTP requests overhead.
Let’s look what can be done to reduce time to render.

1. First, we want to move CSS components to the HEAD section of HTML document so the Stylesheet will be loaded with first priority.
Worse case is when CSS included on the bottom – in this case browser waits till whole document including all components loaded before start rendering. The same thing may happen when CSS included with @import directive, so avoid using @import.

2. If we have several Stylesheets we join them into one bigger CSS file. As described in the previous post about Image Sprites, we want to reduce amount of downloaded components because of HTTP request overhead. And if in case of images there is just a delay before loading next image, in case of Stylesheet it’s a worse case: visitors see blank page until all Stylesheets loaded. One single file loads faster because there will be minimal overhead and usually it compresses better too.

3. We can go even further: if user comes with empty cache we can avoid additional request by including Stylesheet required for the page into HTML. So there will be no requests for CSS at all during loading of the page.
We can preload Stylesheet after the page loaded so for primed cache page views user already has the Stylesheet cached. This way we reduce download size and avoid HTTP requests.
More detailed:
Set a cookie when visitor comes for first time. Check the cookie on every request to determine whether the visitor comes first time or not. This way we can guess cache state of the visitor. Don’t inline Stylesheet component if visitor comes with primed cache, use an external definition instead.
Postload the external Stylesheet component when visitor comes first time (without the cookie). See example below.
Cookie named “jsl” will be set when user sees this page for first time and next requests will use external Stylesheet component.
You can set such cookie in PHP:

$is_first = !isset($_COOKIE['jsl']);
if ($is_first)
setcookie('jsl',1,time()+3600*24,'/','.site.com',false,true);

We’ve supposed that browser cache data will be removed in 24 hours and cookie expiration time set to 24 hours.

The block above will output inline Stylesheet in case user comes for first time otherwise it will output link to the static Stylesheet components. You should place this code inside document head.
It will work correctly even in case user comes with empty cache (for example if our cache got forced out because cache size limit).

Style.css – global CSS file. Probably in real application instead of readfile(’style.css’) you will include only part of the Stylesheet that’s needed to render current page.
To find out CSS that’s really required for current page, you can use a Firefox extension: Dust-Me Selectors (note that this tool may not count CSS rules for elements you assign dynamically in Javascript).

Replace existing opening tag

with

>

We’ve added document onload event handler that will create link DOM element if user comes with empty cache so the components will be already in cache for further page views. The external component will be preloaded only after whole page loaded and rendered so it won’t hurt performance.

Caching HTTP Headers, Last-Modified and ETag

What if we cannot predict lifetime of page content? If we have a page with info that changes unpredictably we still can use browser cache to avoid unneeded traffic.
Using validation mechanism browser sends HTTP request with info about cache entry and server can respond that the content wasn’t changed.
There are two validation methods: one is based on Last-Modified and the other is based on Etag.

Last-Modified
Server sends Last-Modified header with datetime value that means the time when content was changed last time.

Cache-Control: must-revalidate
Last-Modified: 15 Sep 2008 17:43:00 GMT

The first header Cache-Control: must-revalidate means that browser must send validation request every time even if there is already cache entry exists for this object.
Browser receives the content and stores it in the cache along with the last modified value.
Next time browser will send additional header:

If-Modified-Since: 15 Sep 2008 17:43:00 GMT

This header means that browser has cache entry that was last changed 17:43.
Then server will compare the time with last modified time of actual content and if it was changed server will send the whole updated object along with new Last-Modified value.

If there were no changes since the previous request then there will be short empty-body answer:

HTTP/1.x 304 Not Modified

And browser will use the cached content.



( Read more )

Automated Data:URI CSS Sprites

Recently I found another interesting service: DURIS (Data URI Sprites). This is an automated solution for building Data:URI CSS Sprites for background images.

Basically you provide a page URL, then the service loads all styles of the page (both internal and external), finds all background images and encodes them into base64 (for data:URI format). As output, it provides a tar.gz file with HTML file and CSS files with encoded images in textual format.

The HTML contains the original pages with built-in code that checks browser version and plugs the styles with encoded images. There are several version of the styles for different browsers:

  • MHTML – for IE version < 8 (only IE8 supports data:URI )
  • data:URI – for all the other browsers
  • original styles – for the case when Javascript is disabled and for IE7 under Vista (there is a bug related to security issue with cached MHTML background images)

The advantages are:

  • all background images will be loaded in one HTTP request
  • it’s possible to quickly rebuild CSS sprites after making changes

This is a new tool that’s in beta stage so I would not use it in production.
The tool written in Java and the authors are going to opensource it as soon as release-candidate is ready.
(via Habr article in Russian)

I remind that besides Data:URI Sprites we have classic Image Sprites that involve joining images into another big image (opposite to base64 supposed by Data:URI). There are number of tools exist for Image Sprites and most advanced that I know is SmartSprites.

The advantage of Image Sprites over Data:URI Sprites is that it’s supported by almost all browsers including very old versions. The disadvantage is that Image Sprites composition cannot be fully automated and every time should be controlled by web developer.


[via www.webscalingblog.com]

Using CSS Image Sprites

Even fast connections suffer from multiple HTTP request overhead because of network latency and HTTP request processing overhead.
MSIE7 opens no more than 2 connections per domain, Firefox3 – 4-6 connections, Google Chrome – 6 connections.
Let’s suppose that time to first byte is 70ms (typical response time of http://google.com via 1.5 Mbps ADSL connection from Dulles)
If a page view involves loading of 20 images from the same domain, then total overhead in MSIE7 is 20/2*70ms = 700ms. The more images site contains, the more time visitor will have to wait.

The popular solution is to use CSS Image Sprites:
Join design/logo images into one bigger image. Use spriting technique (css background) to show the images on page. An image can be defined via css property background-position.
This approach is compatible with modern browsers (and even with IE4).

Let’s review the process step by step:
Step1.
Replace image entries with



( Read more )

Caching HTTP Headers, Cache-Control: max-age

Caching speeds up repeated page views and saves a lot of traffic by preventing downloading of unc hanged content every page view.
We can use Cache-Control: max-age=… to inform browser that the component won’t be changed for defined period. This way we avoid unneeded further requests if browser already has the component in its cache and therefore primed-cache page views will be performed faster.
Modern browsers able to cache static files even without any cache control headers using some heuristic methods but they will do it more efficient if we define caching headers implicitly.

For Apache2 you can enable max-age using mod_expires:


ExpiresActive On
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 month"
ExpiresByType text/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
ExpiresByType application/x-shockwave-flash "access plus 1 month"

For Lighttpd there is mod_expire module. Enable it in server.modules section:

server.modules = (
...
"mod_expire",
...
)
Then add following directives for directories with static files:
$HTTP["url"] =~ "^/images/" {
expire.url = ( "" => "access 30 days" )
}

Max-age for Nginx server can be enabled using ngx_http_headers_module:

expires max;

Now web server sends the caching header for static files:

Cache-Control: max-age=2592000

In case of design change we should prevent using outdated content that browsers have in their caches. This can be done by adding file versions to filenames:

script.js -> script1.js -> script2.js -> ... etc



( Read more )

GZIP Compression and Javascript Minification

Compressing content on server before sending it over HTTP is a good practice since it saves a lot of traffic and makes transfers faster.
For example, Prototype.js is 123KB size and after Gzip compression it becomes only 28KB.
GZIP Compression also affects HTML so even primed cache requests that downloads mostly only HTML will be faster.
So we should enable server-side compression of textual components: Stylesheets, Javascript, HTML, XML (there is no need to compress images as they already compressed).

For Apache2:
compression done by mod_deflate

AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/x-javascript text/xml

For Lighttpd:
enable mod_compress in lighttpd.conf and add configuration directives:

compress.cache-dir = "/tmp/"
compress.filetype = ("text/plain", "text/html", "application/x-javascript", "text/css", "text/javascript", "text/xml")

Nginx configuration directives:

gzip on;
gzip_min_length 1000;
gzip_types text/html text/plain text/css text/javascript application/x-javascript text/xml;

(See ngx_gzip_module documentation for details)

Besides compression there is minification (removing comments and unnecessary whitespaces) that can further reduce size of Javascript.
Minified Prototype.js becomes 92KB size uncompressed and 23KB compressed, this is 20% less than compressed non-minified script.
There are number of tools for minification exists, I’m using JSMin (http://crockford.com/javascript/jsmin).


[via www.webscalingblog.com]

Optimized Version of Rails MySQL Session Store is Available

The Problem

Original ActiveRecord sessions st ore is slow. It is fine for some low traffic sites, but it is too slow to use it for something larger. First of all, it is slow because ActiveRecord itself is slow. It is powerful ORM framework, but it is obviously overkill for such a simple task as a sessions management.

There are other possible solutions like cookie session store (session size is limited, no sensible data in sessions), memcached (no persistence + harder to implement high-available solutions).

That is why SqlSession store was created. It works with mysql directly with database APIs and works much faster than original AR session store. But sometimes it is still slow too because:

  • it creates/updates session on each hit – every bot or occasional web serfer create session records in your database so you end up with thousands of thousands of useless records in your sessions table while 99% of hits do not require any session updates.
  • it uses 32-characters string as a key for sessions records – all RDBMS work with string keys MUCH slower than with integers, so it would be much better to use integers, but we have these long session IDs and all session stores use them as a table key.
  • it uses auto_increment primary key, which causes table-level locks in InnoDB for all MySQL versions prior to 5.1.21. These table-level locks with unnecessary inserts cause really weird problems for large sites.

The Solution

As a solution for mentioned problems FastSessions Rails plugin was born.

So, first of all, we removed id field from the sessions table, so we do not need to use auto-increment locks. Next step was to make lookups faster and we’ve used a following technique: instead of using (session_id) as a lookup key, we’ve started using (CRC32(session_id), session_id) – two-columns key which really helps MySQL to find sessions faster because key cardinality is higher (so, mysql is able to find a record earlier w/o checking a lots of index rows). We’ve benchmarked this approach and it shows 10-15% performance gain on large sessions tables.



( Read more )