Tuesday, November 07, 2006

How to reduce page loading time.

It is widely accepted that fast-loading pages improve the user experience. In recent years, many sites have started using AJAX techniques to reduce latency. Rather than round-trip through the server retrieving a completely new page with every click, often the browser can either alter the layout of the page instantly or fetch a small amount of HTML, XML, or javascript from the server and alter the existing page. In either case, this significantly decreases the amount of time between a user click and the browser finishing rendering the new content.

However, for many sites that reference dozens of external objects, the majority of the page load time is spent in separate HTTP requests for images, javascript, and stylesheets. AJAX probably could help, but speeding up or eliminating these separate HTTP requests might help more, yet there isn't a common body of knowledge about how to do so.

While working on optimizing page load times for a high-profile AJAX application, I had a chance to investigate how much I could reduce latency due to external objects. Specifically, I looked into how the HTTP client implementation in common browsers and characteristics of common Internet connections affect page load time for pages with many small objects.

Try some of the following:
  • Turn on HTTP keepalives for external objects. Otherwise you add an extra round-trip for every HTTP request. If you are worried about hitting global server connection limits, set the keepalive timeout to something short, like 5-10 seconds. Also look into serving your static content from a different webserver than your dynamic content. Having thousands of connections open to a stripped down static file webserver can happen in like 10 megs of RAM total, whereas your main webserver might easily eat 10 megs of RAM per connection.

  • Load fewer external objects. Due to lower request overhead, one bigger file just loads faster than two smaller ones half its size. Figure out how to globally reference the same one or two javascript files and one or two external stylesheets instead of many; if you have more, try preprocessing them when you publish them. If your UI uses dozens of tiny GIFs all over the place, consider switching to a much cleaner CSS-based design which probably won't need so many images. Or load all of your common UI images in one request using a technique called "CSS sprites".

  • If your users regularly load a dozen or more uncached or uncacheable objects per page, consider evenly spreading those objects over four hostnames. This usually means your users can have 4x as many outstanding connections to you. Without HTTP pipelining, this results in their average request latency dropping to about 1/4 of what it was before.

    When you generate a page, evenly spreading your images over four hostnames is most easily done with a hash function, like MD5. Rather than having all tags load objects from http://static.example.com/, create four hostnames (e.g. static0.example.com, static1.example.com, static2.example.com, static3.example.com) and use two bits from an MD5 of the image path to choose which of the four hosts you reference in the tag. Make sure all pages consistently reference the same hostname for the same image URL, or you'll end up defeating caching.

    Beware that each additional hostname adds the overhead of an extra DNS lookup and an extra TCP three-way handshake. If your users have pipelining enabled or a given page loads fewer than around a dozen objects, they will see no benefit from the increased concurrency and the site may actually load more slowly. The benefits only become apparent on pages with larger numbers of objects. Be sure to measure the difference seen by your users if you implement this.

  • Possibly the best thing you can do to speed up pages for repeat visitors is to allow static images, stylesheets, and javascript to be unconditionally cached by the browser. This won't help the first page load for a new user, but can substantially speed up subsequent ones.

    Set an Expires header on everything you can, with a date days or even months into the future. This tells the browser it is okay to not revalidate on every request, which can add latency of at least one round-trip per object per page load for no reason.

    Instead of relying on the browser to revalidate its cache, if you change an object, change its URL. One simple way to do this for static objects if you have staged pushes is to have the push process create a new directory named by the build number, and teach your site to always reference objects out of the current build's base URL. (Instead of you'd use . When you do another build next week, all references change to .) This also nicely solves problems with browsers sometimes caching things longer than they should -- since the URL changed, they think it is a completely different object.

    If you conditionally gzip HTML, javascript, or CSS, you probably want to add a "Cache-Control: private" if you set an Expires header. This will prevent problems with caching by proxies that won't understand that your gzipped content can't be served to everyone. (The Vary header was designed to do this more elegantly, but you can't use it because of IE brokenness.)

    For anything where you always serve the exact same content when given the same URL (e.g. static images), add "Cache-Control: public" to give proxies explicit permission to cache the result and serve it to different users. If a local cache has the content, it is likely to have much less latency than you; why not let it serve your static objects if it can?

    Avoid the use of query params in image URLs, etc. At least the Squid cache refuses to cache any URL containing a question mark by default. I've heard rumors that other things won't cache those URLs at all, but I don't have more information.

  • On pages where your users are often sent the exact same content over and over, such as your home page or RSS feeds, implementing conditional GETs can substantially improve response time and save server load and bandwidth in cases where the page hasn't changed.

    When serving a static files (including HTML) off of disk, most webservers will generate Last-Modified and/or ETag reply headers for you and make use of the corresponding If-Modified-Since and/or If-None-Match mechanisms on requests. But as soon as you add server-side includes, dynamic templating, or have code generating your content as it is served, you are usually on your own to implement these.

    The idea is pretty simple: When you generate a page, you give the browser a little extra information about exactly what was on the page you sent. When the browser asks for the same page again, it gives you this information back. If it matches what you were going to send, you know that the browser already has a copy and send a much smaller 304 (Not Modified) reply instead of the contents of the page again. And if you are clever about what information you include in an ETag, you can usually skip the most expensive database queries that would've gone into generating the page.

  • Minimize HTTP request size. Often cookies are set domain-wide, which means they are also unnecessarily sent by the browser with every image request from within that domain. What might've been a 400 byte request for an image could easily turn into 1000 bytes or more once you add the cookie headers. If you have a lot of uncached or uncacheable objects per page and big, domain-wide cookies, consider using a separate domain to host static content, and be sure to never set any cookies in it.

  • Minimize HTTP response size by enabling gzip compression for HTML and XML for browsers that support it. For example, the 17k document you are reading takes 90ms of the full downstream bandwidth of a user on 1.5Mbit DSL. Or it will take 37ms when compressed to 6.8k. That's 53ms off of the full page load time for a simple change. If your HTML is bigger and more redundant, you'll see an even greater improvement.

    If you are brave, you could also try to figure out which set of browsers will handle compressed Javascript properly. (Hint: IE4 through IE6 asks for its javascript compressed, then breaks badly if you send it that way.) Or look into Javascript obfuscators that strip out whitespace, comments, etc and usually get it down to 1/3 to 1/2 its original size.

  • Consider locating your small objects (or a mirror or cache of them) closer to your users in terms of network latency. For larger sites with a global reach, either use a commercial Content Delivery Network, or add a colo within 50ms of 80% of your users and use one of the many available methods for routing user requests to your colo nearest them.

  • Regularly use your site from a realistic net connection. Convincing the web developers on my project to use a "slow proxy" that simulates bad DSL in New Zealand (768Kbit down, 128Kbit up, 250ms RTT, 1% packet loss) rather than the gig ethernet a few milliseconds from the servers in the U.S. was a huge win. We found and fixed a number of usability and functional problems very quickly.

    To implement the slow proxy, I used the netem and HTB kernel modules available in the Linux 2.6 kernel, both of which are set up with the tc command line tool. These offer the most accurate simulation I could find, but are definitely not for the faint of heart. I've not used them, but supposedly Tamper Data for Firefox, Fiddler for Windows, and Charles for OSX can all rate-limit and are probably easier to set up, but they may not simulate latency properly.

  • Use Google's Load Time Analyzer extension for Firefox from a realistic net connection to see a graphical timeline of what it is doing during a page load. This shows where Firefox has to wait for one HTTP request to complete before starting the next one and how page load time increases with each object loaded. The Tamper Data extension can offer similar data in less easy to interpret form. And the Safari team offers a tip on a hidden feature in their browser that offers some timing data too.

    Or if you are familiar with the HTTP protocol and TCP/IP at the packet level, you can watch what is going on using tcpdump, ngrep, or ethereal. These tools are indispensible for all sorts of network debugging.

  • Try benchmarking common pages on your site from a local network with ab, which comes with the Apache webserver. If your server is taking longer than 5 or 10 milliseconds to generate a page, you should make sure you have a good understanding of where it is spending its time.

    If your latencies are high and your webserver process (or CGI if you are using that) is eating a lot of CPU during this test, it is often a result of using a scripting language that needs to recompile your scripts with every request. Software like eAccelerator for PHP, mod_perl for perl, mod_python for python, etc can cache your scripts in a compiled state, dramatically speeding up your site. Beyond that, look at finding a profiler for your language that can tell you where you are spending your CPU. If you fix that, your pages will load faster and you'll be able to handle more traffic with fewer machines.

    If your site relies on doing a lot of database work or some other time-consuming task to generate the page, consider adding server-side caching of the slow operation. Most people start with writing a cache to local memory or local disk, but that starts to fall down if you expand to more than a few web server machines. Look into using memcached, which essentially creates an extremely fast shared cache that's the combined size of the spare RAM you give it off of all of your machines. It has clients available in most common languages.

  • (Optional) Petition browser vendors to turn on HTTP pipelining by default on new browsers. Doing so will remove some of the need for these tricks and make much of the web feel much faster for the average user. (Firefox has this disabled supposedly because some proxies, some load balancers, and some versions of IIS choke on pipelined requests. But Opera has found sufficient workarounds to enable pipelining by default. Why can't other browsers do similarly?)

Ashko

IE7 and AJAX. IE7 has added native XMLHTTPRequest object

Native XMLHTTPRequest object

I’m excited to mention that IE7 will support a scriptable native version of XMLHTTP. This can be instantiated using the same syntax across different browsers and decouples AJAX functionality from an ActiveX enabled environment.

What is XMLHTTP?

XMLHTTP was first introduced to the world as an ActiveX control in Internet Explorer 5.0. Over time, this object has been implemented by other browsing platforms, and is the cornerstone of “AJAX” web applications. The object allows web pages to send and receive XML (or other data) via the HTTP protocol. XMLHTTP makes it possible to create responsive web applications that do not require redownloading the entire page to display new data. Popular examples of AJAX applications include the Beta version of Windows Live Local, Microsoft Outlook Web Access, and Google’s GMail.

Charting the changes: XMLHTTP in IE7 vs. IE6

In IE6 and below, XMLHTTP is implemented as an ActiveX object provided by MSXML.

In IE7, XMLHTTP is now also exposed as a native script object. Users and organizations that choose to disable ActiveX controls can still use XMLHTTP based web applications. (Note that an organization may use Group Policy or IE Options to disable the new native XMLHTTP object if desired.) As part of our continuing security improvements we now allow clients to configure and customize a security policy of their choice and simultaneously retain functionality across key AJAX scenarios.

IE7’s implementation of the XMLHTTP object is consistent with that of other browsers, simplifying the task of cross-browser compatibility. Using just a bit of script, it’s easy to build a function which works with any browser that supports XMLHTTP:

if (window.XMLHttpRequest){

// If IE7, Mozilla, Safari, etc: Use native object
var xmlHttp = new XMLHttpRequest()

}
else
{
if (window.ActiveXObject){

// ...otherwise, use the ActiveX control for IE5.x and IE6
var xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}

}

Note that IE7 will still support the legacy ActiveX implementation of XMLHTTP alongside the new native object, so pages currently using the ActiveX control will not require rewrites.

I look forward to hearing any feedback and suggestions.


Ashko

Monday, November 06, 2006

Mistakes made when developing with Ajax .


Using Ajax for the sake of Ajax.

Sure Ajax is cool, and developers love to play with cool technology, but Ajax is a tool, not a toy. A lot of Ajax isn’t seriously needed to improve usability but rather experiments in what Ajax can do or trying to fit Ajax somewhere where it isn’t needed.

Breaking the back button

The back button is a great feature of the standard web site user interface. Unfortunately, the back button doesn’t mesh very well with Javascript. Keeping back button functionality is one reason not to go with a pure Javascript web app.

Keep in mind however that good web design provides the user with everything they would need to successfully navigate your site, and never relies on web browser controls.

Not giving immediate visual cues for clicking widgets

If something I’m clicking on is triggering Ajax actions, you have to give me a visual cue that something is going on. An example of this is GMail loading button that is in the top right. Whenever I do something in GMail, a little red box in the top right indicates that the page is loading, to make up for the fact that Ajax doesn’t trigger the normal web UI for new page loading.

Leaving offline people behind

As web applications push the boundaries further and further, it becomes more and more compelling to move all applications to the web. The provisioning is better, the world-wide access model is great, the maintenance and configuration is really cool, the user interface learning curve is short.

However, with this new breed of Ajax applications, people who have spotty internet connections or people who just don’t want to switch to the web need to be accomodated as well. Just because technology ‘advances’ doesn’t mean that people are ready and willing to go with it. Web application design should at least consider offline access. With GMail it’s POP, Backpackit has SMS integration. In the Enterprise, it’s web-services.
Don’t make me wait

With Firefox tabs, I can manage various waits at websites, and typically I only have to wait for a page navigation. With AJAX apps combined with poor network connectivity/bandwidth/latency I can have a really terrible time managing an interface, because every time I do something I have to wait for the server to return a response. However, remember that the ‘A’ in AJAX stands for ‘Asynchronous’, and the interaction can be designed so that the user is not prevented from continuing to work on the page while the earlier request is processed.

Sending sensitive information in the clear

The security of AJAX applications is subject to the same rules as any web application, except that once you can talk asynchronously to the server, you may tend to write code that is very chatty in a potentially insecure way. All traffic must be vetted to make sure security is not compromised.

Assuming AJAX development is single platform development.

Ajax development is multi-platform development. Ajax code will run on IE’s javascript engine, Spidermonkey (Mozilla’s js engine), Rhino (a Java js implementation, also from Mozilla), or other minor engines that may grow into major engines. So it’s not enough just to code to JavaScript standards, there needs to be real-world thorough testing as well. A major obstacle in any serious Javascript development is IE’s buggy JS implementation, although there are tools to help with IE JS development.

Forgetting that multiple people might be using the same application at the same time

In the case of developing an Intranet type web application, you have to remember that you might have more than one person using the application at once. If the data that is being displayed is dynamically stored in a database, make sure it doesn’t go “stale” on you.

Too much code makes the browser slow

Ajax introduces a way to make much more interesting javascript applications, unfortunately interesting often means more code running. More code running means more work for the browser, which means that for some javascript intensive websites, especially inefficiently coded ones, you need to have a powerful CPU to keep the functionality zippy. The CPU problem has actually been a limit on javascript functionality in the past, and just because computers have gotten faster doesn’t mean the problem has disappeared.

Not having a plan for those who do not enable or have JavaScript.

According to the W3 schools browser usage statistics, which if anything are skewed towards advanced browsers, 11% of all visitors don’t have JavaScript. So if your web application is wholly dependent on JavaScript, it would seem that you have potentially cut a tenth of your audience.

Blinking and changing parts of the page unexpectedly

The first A in Ajax stands for asynchronous. The problem with asynchronous messages is that they can be quite confusing when they pop in unexpectedly. Asynchronous page changes should only ever occur in narrowly defined places and should be used judiciously, flashing and blinking in messages in areas I don’t want to concentrate on harkens back to days of the html blink tag. “Yellow Fade”, “One Second Spotlight” and other similar techniques are used to indicate page changes unobtrusively.

Not using links I can pass to friends or bookmark

Another great feature of websites is that I can pass URLs to other people and they can see the same thing that I’m seeing. I can also bookmark an index into my site navigation and come back to it later. Javascript, and thus Ajax applications, can cause huge problems for this model of use. Since the Javascript is dynamically generating the page instead of the server, the URL is cut out of the loop and can no longer be used as an index into navigation. This is a very unfortunate feature to lose, many Ajax webapps thoughtfully include specially constructed permalinks for this exact reason.

Blocking Spidering

Ajax applications that load large amounts of text without a reload can cause a big problem for search engines. This goes back to the URL problem. If users can come in through search engines, the text of the application needs to be somewhat static so that the spiders can read it.

Asynchronously performing batch operations

Sure with Ajax you can make edits to a lot of form fields happen immediately, but that can cause a lot of problems. For example if I check off a lot of check boxes that are each sent asynchronously to the server, I lose my ability to keep track of the overall state of checkbox changes and the flood of checkbox change indications will be annoying and disconcerting.

Scrolling the page and making me lose my place

Another problem with popping text into a running page is that it can effect the page scroll. I may be happily reading an article or paging through a long list, and an asynchronous javascript request will decide to cut out a paragraph way above where I’m reading, cutting my reading flow off. This is obviously annoying and it wastes my time trying to figure out my place. But then again, that would be a very stupid way to program a page, with or without AJAX.

Inventing new UI conventions

A major mistake that is easy to make with Ajax is: ‘click on this non obvious thing to drive this other non obvious result’. Sure, users who use an application for a while may learn that if you click and hold down the mouse on this div that you can then drag it and permanently move it to this other place, but since that’s it’s not in the common user experience, you increase the time and difficulty of learning the application, which is a major negative for any application. On the plus side, intuitiveness is a function of learning, and AJAX is popularising many new conventions which will become intuitive as time goes by. The net result will be greater productivity once the industry gets over the intuitiveness hump.

Character Sets

One big problem with using AJAX is the lack of support for character sets. You should always set the content character set on the server-side as well as encoding any data sent by Javascript. Use ISO-8859-1 if you use plain english, or UTF-8 if you use special characters, like æ, ø and å (danish special characters) Note: it is usually a good idea to go with utf-8 nowadays as it supports many languages).

Changing state with links (GET requests)

The majority of Ajax applications tend to just use the GET method when working with AJAX. However, the W3C standards state that GET should only be used for retrieving data, and POST should only be used for setting data. Although there might be no noticable difference to the end user, these standards should still be followed to avoid problems with robots or programs such as Google Web Accelerator.

Not cascading local changes to other parts of the page

Since Ajax/Javascript gives you such specific control over page content, it’s easy to get too focused on a single area of content and miss the overall integrated picture. An example of this is the Backpackit title. If you change a Backpackit page title, they immediately replace the title, they even remember to replace the title on the right, but they don’t replace the head title tag with the new page title. With Ajax you have to think about the whole picture even with localized changes.

Problem reporting

In a traditional server-side application, you have visibility into every exception, you can log all interesting events and benchmarks, and you can even record and view (if you wish) the actual HTML that the browser is rendering. With client-side applications, you may have no idea that something has gone wrong if you don’t know how to code correctly and log exceptions from the remotely called pages to your database.

Return on Investment

Sometimes AJAX can impressively improve the usability of an application (a great example is the star-rating feedback on Netflix), but more often you see examples of expensive rich-client applications that were no better than the plain HTML versions.

Mimicing browser page navigation behavior imperfectly

One example of this is blinklist Ajax paging mechanism on the front page. As you click to see another page of links, ajax fills in the next page. Except that if you are used to a browser experience, you probably expect to go to the top of the page when you hit next page, something JavaScript driven page navigation doesn’t do. BlinkList actually anticipates this and tries to counteract by manipulating your scrolling to scroll upwards until you hit the top. Except this can be slow and if you try scrolling down you will fight the upwards scrolling JavaScript and it won’t let you scroll down. But then again, that is very stupid way to program a page, with or without AJAX.

Another Tool

It seems everyone has forgotten that Ajax is just another tool in the toolbox for Web Design. You can use it or not and misuse it or not. The old 80/20 rule always applies to applications (if you cover 80% of what all users want/need then you have a viable app) and if you lose 11% of your audience because they don’t switch on their javascript then you have to ask yourself if changing your app is worth capturing that 11% or stick with 89% that are currently using it and move on to something else. Also web apps should take advantage of all tricks to enable them to function quickly and efficiently. If that means using javascript for some part, Ajax for another and ASP callbacks for a third, so be it.

Ashko