Andreas Grabner About the Author

Andreas Grabner has been helping companies improve their application performance for 15+ years. He is a regular contributor within Web Performance and DevOps communities and a prolific speaker at user groups and conferences around the world. Reach him at @grabnerandi

Ajax Best Practices: Reduce and Aggregate similar XHR calls

My mobile contract is expiring soon, so I went online and checked the different mobile companies for their offerings. All of these sites have a page where you get an overview of all the different phone models available. One one page I noticed that the list of phones started with an empty grid – and then – slowly – images appeared and after that some overlay information on the phone itself was added (such as a link on the facebook, msn or skype page of the phone manufacturer). From a time perspective it took about 2 seconds till I saw something on the page, after 4 got to see the empty grid and after 15 seconds everything was loaded. That made me curious about the internals of that page.

Analyzing a heavy AJAX Page

I start by using dynaTrace AJAX Edition and browse to the same page (I use the latest Beta build for my analysis). When I am done I go back to the AJAX Edition and open the Performance Report. The first thing I do is check the timeline which gives me an overview of all activities on the page from when I entered the URL until it was fully loaded. The following image shows the timeline of this page:

Timeline shows some initial XHR calls and many XHR calls triggered by the onLoad event handler

Timeline shows some initial XHR calls and many XHR calls triggered by the onLoad event handler

If I move the mouse over the small icons in the events row I can see that there are many XHR calls triggered at the onLoad event. That makes me move over to the JavaScript/AJAX Tab in the Performance report. It tells me that there are a total of 50 XHR calls on that page:

3 Initial XHR calls followed by one XHR call for every product on the page

3 Initial XHR calls followed by one XHR call for every product on the page

Analyzing the XHR Calls

Right away I assume that the number of XHR calls is somewhat related with the number of phones that are displayed on the site. First however I look at the XHR calls that are made in the very beginning. A double click on the first XHR icon in the timeline opens the JavaScript that triggered these XHR calls and also shows me the list of individual XHR calls that were made at that point in time:

3 XHR calls are initially called to retrieve customer, shopping cart and product information

3 XHR calls are initially called to retrieve customer, shopping cart and product information

All 3 XHR calls return JSON objects where the third call – to retrieve product information – returns an array of 47 products including product name, price information and a product id:

First part of the XHR Response containing a JSON Array with product informations

First part of the XHR Response containing a JSON Array with product information

If we drill into the actual JavaScript PurePath we soon find the 47 XHR calls that are made – one for each product – to retrieve additional information:

The for loop is used to make an XHR call for every product to retrieve more product information

The for loop is used to make an XHR call for every product to retrieve more product information

We can see that every handler in the for each loop takes longer to execute and that most of the time spent in each of these loop iterations is waiting for the XHR Request to come back with another JSON object that holds some additional product information. Why this is taking longer with every call? A look at the Network View shows us what the problem is:

All requests (XHR + Image requests) are sent to the same domain and therefore have to wait to be served

All requests (XHR + Image requests) are sent to the same domain and therefore have to wait to be served

The increasing time component is the wait time for every request. A browser only has a limited number of physical network connections it opens to each domain. In my case it uses 2 connections per domain. With a total number of 47 XHR calls and 47 product images but only 2 connections available to make these calls we end up with a very high wait time for each of those requests.

Solution: Reducing network roundtrips by reducing requests

The solution is rather easy – at least in theory. The goal is to reduce the number of network roundtrips. Not only does this save you time lost with network latency – it also saves time on the server as it has fewer requests to handle and it solves the problem in the browser by not needing to wait so long for an open connection as we have far fewer requests. In this case – instead of making one XHR call for every one of the 47 products – I would just make a single call to retrieve the product information in one roundtrip for all of them. Or – even better – just include that information in the initial XHR call that gets the list of products. With that approach we can save 46 or even 47 roundtrips.

This would have a major impact on the web site performance. If we would then also merge the 47 images files into a single file using CSS Sprites we can get rid of another 46 roundtrips. I know – this is the theory – it might be not that easy to implement this on every page based on the architecture or maybe because there is not time or people that could actually do this – but – if I do the math here: instead of waiting 15 seconds till the page is fully loaded I assume the page can be loaded in 6 or 7 seconds. This would accelerate page loading by more than 50% speed up of this page. And – as we have recently learned from Velocity – Speed is Money!!

Conclusion

It is not the first time I’ve seen this exact implementation and therefore I assume it is not the last implementation out there that solves a problem like a product catalogue like this. Be aware of too many XHRs. Reduce the roundtrips. Another solution would be to use different domains for your XHR calls and your images. This gives the browser additional physical connections and speeds up the download. It’s not the perfect solution – but it is a good workaround :-)

As always – let me know what you think – and – here is some more material that you might be interested in: Best Practices on Web Performance Optimization, Best Practices on JavaScript/AJAX Performance or the Top 10 Performance Problems from Zappos, Monster and Co

Comments

  1. Jose Noheda says:

    The interesting point would be how to batch calls. DWR for can do it easily. How else?

  2. I would change the XHR Request call and pass in all the product ids that I want to get the additional information for. The response would then contain an array of product information for each requested product.

  3. Sprites are only really practical with static sets of images that your CSS will know how to deal with in advance. Dynamically creating sprites and CSS on the fly would be an awful resource hog for a web server.

    You have to think of caching too. Imagine someone vists this site and they see a 80×120 thumbnail image of an iPhone along with 4 other phones on, say, a special offers page. Imagine the 5 thumbnails are in a sprite.

    The user then clicks on the ‘Apple’ section of the site and they see the exact same image, except it’s part of a different data set, a new sprite, therefore uncached. Doesn’t really make sense.

    I would bet (pure speculation of course, but I may test this at home later) that it would actually be quicker to load the 46 images in the browser as normal than dynamically create a sprite, a css file and serve it to the browser.

    The most practical way to speed up the image and css requests would be to serve those files from (pseudo) subdomains such as img.drei.at and css.drei.at and implement a lazyloading script (http://www.appelsiini.net/projects/lazyload).

  4. Paul, all great points. I’ve been writing about CSS Sprites and domain sharding in my previous blog posts. The point that I wanted to make with this blog is to highlight some bad practices I’ve seen with XHR calls. This page is not the only one where I’ve seen this exact behavior. Making tons of individual XHR calls is very inefficient. It is similar to “chatty” remoting calls on the server-side. Basically these “bad practices” from the server-side world now make it to the client-side where latency hits you much harder than on the server-side
    let me know if you did your test scenario as you described and what the outcome is. we are always looking for new ideas on how to speed up web sites.

  5. Helpful post… Thanks for sharing.

  6. This is great information and thank you very much for your time to post this help full information.

Comments

*


3 + = nine