Yesterday at work, we ran into a very interesting problem involving a jQuery auto-suggest feature on one of our client sites. We had implemented auto-suggest on this particular site several times before and it had always proved to be very zippy and responsive. This time, however, the "suggest" page requests were taking 5, 6, sometimes 8 seconds to complete; this kind of delay clearly defeats the purpose of auto-suggest. But, more than that, I was getting extremely frustrated with this seemingly nonsensical delay that none of the other auto-suggest instances were exhibiting.
After checking the HTML, the jQuery, and the SQL query powering the auto-suggest, I was just about at my wit's end; then, blankly staring at FireBug's network activity in disbelief, it finally dawned on me! This particular auto-suggest feature's result set included thumbnail images of products (it was an e-commerce website). That was the problem: there's only so many parallel requests that the browser can make to the same domain at the same time. What was happening here was that the product thumbnails were hogging up all of the available HTTP requests; as such, when the auto-suggest went to make a subsequent request, it got queued after the pending image requests.
As you can see in the video, when the auto-suggest feature launches a second request, it gets queued after the all of the pending image requests. My particular browser (all browsers are different and can be custom configured) can handle 6 parallel requests to the same domain. As such, you can see in FireBug that not even all of the image thumbnail requests can be made at the same time; of the 10, only the first 6 are actually processing, leaving 4 pending. Then, only when the first 6 thumbnails load, the last 4 can be processed. At that point, since there are only 4 concurrent requests, the second auto-suggest request can finally be processed.
The solution to this is not to remove images from the auto-suggest results - remember, we want to create a rich, interactive experience for our users; the solution is much more core to the problem. The thumbnails aren't the issue, it's the nature of the parallel requests. The browser has limits on how many parallel requests it can make to the same domain; so, to solve the problem, rather than removing the images, we "simply" have to pull them from a different domain. Once we do that, the browser will have no problems processing more than 6 parallel requests.
As you can see in this video, once the thumbnails are being pulled from a different domain than then auto-suggest results, the browser no longer needs to queue the auto-suggest results. As such, they come back fast even when there are many pending images still loading.
To be honest, multi-domain strategies are something that I understand in theory, but have never really put into effect. Things like loading binary assets off of Amazon S3 or sub-domains makes perfect sense - I just haven't implemented them all that much in production. After seeing this problem, however, it definitely feels like something that I need to start implementing as best practice. Whether or not I need it for a particular site, I certainly don't ever want to run into a situation where domain-use prevents me from delivering the most compelling user experience possible.
While the code I was using is not really the point of this post, I will list it below for reference. Here is the main HTML page. Please keep in mind that this was not supposed to be an accurate auto-suggest feature - I just needed something that "looked" like auto-suggest to test the feature:
Here is the code that gathers the auto-suggest results. Notice that I have the code for using two different domains for the IMG SRC attributes.
<!--- Param the query value. ---> <cfparam name="url.query" type="string" /> <!--- Make sure the query never goes more than 10 characters - this is ONLY for demo purposes. ---> <cfset url.query = left( url.query, 10 ) /> <!--- Store the content of the response. For demo purposes, we are simply going to make the results (10) minus the number of letters. ---> <cfsavecontent variable="results"> <cfoutput> <!--- Local domain. ---> <cfset domain = "./" /> <!--- Different domain. ---> <!--- <cfset domain = "http://localhost:8501/jquery/autosuggest/" /> ---> <cfloop index="index" from="#len( url.query )#" to="10" step="1"> <!--- When we make the results line item, notice that we are setting the value of the IMG tag to be a ColdFusion page - this will come into play with the delayed time. NOTE: I am using the randRange() function to prevent any caching attempts by the browser. ---> <a href="##"> <img src="#domain#img.cfm?id=#randRange( 1, 99999 )#" /> <strong>Tanya Hyde</strong> One of the hottest female bodybuilders. </a> </cfloop> </cfoutput> </cfsavecontent> <!--- Convert the response to a binary for streaming. ---> <cfset binaryResponse = toBinary( toBase64( trim( results ) ) ) /> <!--- Set the content length so the browser knows how much to content to expect. ---> <cfheader name="content-length" value="#arrayLen( binaryResponse )#" /> <!--- Stream the content back. ---> <cfcontent type="text/html" variable="#binaryResponse#" />
And, here is the page that loaded the image thumbnail. The only reason this page had to be a ColdFusion page, rather than a binary URI, was to allow ColdFusion to simulate the network latency. I had to sleep the requests so that they didn't complete immediately.
<!--- Sleep the thread for a few seconds. To simulate network delays and request latency. ---> <cfthread action="sleep" duration="#(10 * 1000)#" /> <!--- Simply stream the image back to the client. ---> <cfcontent type="image/*" file="#expandPath( './thumbnail.jpg' )#" />
Anyway, this was just an interesting problem that had me stumped for a good thirty minutes. It definitely gives me a lot of food for thought on what I want to consider "best practices" for asset delivery going forward.
Want to use code from this post? Check out the license.