Inline JavaScript Experiment

Hey Guys,

I would say most websites have a bit of JavaScript in their header section. I setup an experiment to see which of the following exhibits would process faster:

Exhibit A

<script type="text/javascript" src="http://cdn.green-watch.org/javascript/siteJavascript.cfm"></script>

Exhibit B

<script type="text/javascript">
// <![CDATA[
var bodyHasLoaded=0; var swfObjectHasLoaded=0; var swfObjectIsLoading=0; var javascriptReady=false; var protoaculousHasLoaded=0; var myDirectory="http://"+document.domain; var myDirectory2="http://cdn.green-watch.org"; function updateBody() { } function addLoadEvent(func) { if(bodyHasLoaded==1) { func(); } else { var oldonload=window.onload; if (typeof window.onload!='function') { window.onload=func; } else { window.onload=function() { if(oldonload) { oldonload(); } func(); }; } } } function firstOnBodyLoadedEvent() { bodyHasLoaded=1; } addLoadEvent(firstOnBodyLoadedEvent); function GetXmlHttpObject(handler) { var objXMLHttp=null; if(window.XMLHttpRequest) { objXMLHttp=new XMLHttpRequest() } else if(window.ActiveXObject) { objXMLHttp=new ActiveXObject("Microsoft.XMLHTTP"); } return objXMLHttp; } function importZapatecMenuJS(onLoadFunction) { var headID=document.getElementsByTagName("head")[0]; var zpmenuNode=document.createElement('script'); zpmenuNode.type='text/javascript'; zpmenuNode.onload=function() { var myMenu=new Zapatec.Menu({theme:"/inc/barblue.css",source:"menu-items"}); var myMenuBar2=document.getElementById("noScriptMenu"); myMenuBar2.style.display="none"; var myMenuBar=document.getElementById("menu"); myMenuBar.style.display="inline"; if(onLoadFunction!='null') { onLoadFunction(); } }; zpmenuNode.onreadystatechange=function() { if(zpmenuNode.readyState=='complete'||zpmenuNode.readyState=='loaded') { var myMenu=new Zapatec.Menu({theme:"/inc/barblue.css",source:"menu-items"}); var myMenuBar2=document.getElementById("noScriptMenu"); myMenuBar2.style.display="none"; var myMenuBar=document.getElementById("menu"); myMenuBar.style.display="inline"; if(onLoadFunction!='null') { onLoadFunction(); } } }; zpmenuNode.src=myDirectory2+'/javascript/zpmenu.cfm'; headID.appendChild(zpmenuNode); } function importZapatecCalendarJS(onLoadFunction) { var headID = document.getElementsByTagName("head")[0]; var zpCalendarNode=document.createElement('script'); zpCalendarNode.type='text/javascript'; zpCalendarNode.onload=function() { if(onLoadFunction!='null') { importZapatecCalendarJS2(onLoadFunction); } }; zpCalendarNode.onreadystatechange=function() { if(zpmenuNode.readyState=='complete'||zpmenuNode.readyState=='loaded') { if(onLoadFunction!='null') { importZapatecCalendarJS2(onLoadFunction); } } }; zpCalendarNode.src=myDirectory2+'/zapatec/zpcal/src/calendar.js'; headID.appendChild(zpCalendarNode); } function importZapatecCalendarJS2(onLoadFunction) { var headID = document.getElementsByTagName("head")[0]; var zpCalendarNode2=document.createElement('script'); zpCalendarNode2.type='text/javascript'; zpCalendarNode2.onload=function() { if(onLoadFunction!='null') { onLoadFunction(); } }; zpCalendarNode2.onreadystatechange=function() { if(zpCalendarNode2.readyState=='complete'||zpCalendarNode2.readyState=='loaded') { if(onLoadFunction!='null') { onLoadFunction(); } } }; zpCalendarNode2.src=myDirectory2+'/zapatec/zpcal/lang/calendar-en.js'; headID.appendChild(zpCalendarNode2); } function importPrototypeJS() { var headID=document.getElementsByTagName("head")[0]; var prototypeNode=document.createElement('script'); prototypeNode.type='text/javascript'; prototypeNode.src=myDirectory2+'/javascript/prototype.cfm'; headID.appendChild(prototypeNode); } function importProtoaculousJS(onLoadFunction) { var headID=document.getElementsByTagName("head")[0]; var protoaculousNode=document.createElement('script'); protoaculousNode.type='text/javascript'; protoaculousNode.onload=function() { protoaculousHasLoaded=1; if(onLoadFunction!='null') { onLoadFunction(); } }; protoaculousNode.onreadystatechange=function() { if(protoaculousNode.readyState=='complete'||protoaculousNode.readyState=='loaded') { protoaculousHasLoaded=1; if(onLoadFunction!='null') { onLoadFunction(); } } }; protoaculousNode.src=myDirectory2 +'/javascript/protoaculous.cfm'; headID.appendChild(protoaculousNode); } function importSwfObjectJS(hookWheelMouse,onLoadFunction) { if(swfObjectIsLoading==1) { return; } else { swfObjectIsLoading=1; } var headID=document.getElementsByTagName("head")[0]; var swfobjectNode=document.createElement('script'); swfobjectNode.type='text/javascript'; swfobjectNode.onload=function() { swfObjectHasLoaded=1; if(onLoadFunction!=null) { onLoadFunction(); } if(hookWheelMouse==1) { hookMouseWheel(); } }; swfobjectNode.onreadystatechange=function() { if(swfobjectNode.readyState=='complete'||swfobjectNode.readyState=='loaded') { swfObjectHasLoaded=1; if(onLoadFunction!=null) { onLoadFunction(); } if(hookWheelMouse==1) { hookMouseWheel(); } } }; swfobjectNode.src=myDirectory2+'/javascript/swfObject.cfm'; headID.appendChild(swfobjectNode); } function hookMouseWheel() { if (window.addEventListener) { window.addEventListener('DOMMouseScroll',onMouseWheel,false); } window.onmousewheel=document.onmousewheel=onMouseWheel; } function isMouseOverSwf(mEvent) { var elem; if(mEvent.srcElement) { elem=mEvent.srcElement.nodeName; } else if(mEvent.target) { elem=mEvent.target.nodeName; } if(elem.toLowerCase()=="object"||elem.toLowerCase()=="embed") { return true; } return false; } function onMouseWheel(event) { var delta=0; if(!event) { event=window.event; } if(event.wheelDelta) { delta=event.wheelDelta/120; if(window.opera) { delta=-delta; } } else if(event.detail) { delta=-event.detail/3; } if(isMouseOverSwf(event)) { return cancelMouseEvent(event); } return true; } function cancelMouseEvent(e) { e=e?e:window.event; if (e.stopPropagation) { e.stopPropagation(); } if(e.preventDefault) { e.preventDefault(); } e.cancelBubble=true; e.cancel=true; e.returnValue=false; return false; } function enableCityAutoCompleter(myFunctionName) { var myScript=myDirectory+"/scripts/getCities.cfm"; new Ajax.Autocompleter("CITY_NAME","CITY_HINT",myScript, { minChars:1, frequency:0.3, callback:cityNameCallback, afterUpdateElement:myFunctionName }); } function cityNameCallback(element,entry) { var state=document.getElementById("STATE_ID").value; var country=document.getElementById("COUNTRY_ID").value; var myStr="&COUNTRY_ID="+country+"&STATE_ID="+state; return entry+myStr; } function enableBizNameAutoCompleter(myFunctionName) { var myScript=myDirectory+"/scripts/autocompleters/getBizNames.cfm"; new Ajax.Autocompleter("BUSINESS_NAME","BUSINESS_NAME_HINT",myScript, { minChars:1, frequency:0.3, afterUpdateElement:myFunctionName} ); } function showAdSpace() { if(typeof adMaxModifier=='undefined') { adMaxModifier=0; } if(typeof adPreference=='undefined') { adPreference="BUSINESS"; } if(typeof adKeywords=='undefined') { adKeywords=""; } var rightDiv=document.getElementById('col-r'); var leftDiv=document.getElementById('col-l'); var rightHeight=rightDiv.offsetHeight; var leftHeight=leftDiv.offsetHeight; var maxAds=Math.floor((rightHeight-leftHeight)/127)+adMaxModifier; if(maxAds>0) { xmlLeftAdSpace=GetXmlHttpObject(); if(xmlLeftAdSpace==null) { return; } url=myDirectory+"/ADS/showSideAds.cfm?maxAds="+maxAds+"&adKeywords="+adKeywords;url+="&adPreference="+adPreference+"&sid="+Math.random(); xmlLeftAdSpace.open("GET",url,true); xmlLeftAdSpace.onreadystatechange=function() { var sideAdDiv=document.getElementById('MY_ADSPACE'); if(xmlLeftAdSpace.readyState==4&&xmlLeftAdSpace.responseText!='') { sideAdDiv.innerHTML=xmlLeftAdSpace.responseText;sideAdDiv.style.display="block"; } else if(xmlLeftAdSpace.readyState==4) { sideAdDiv.style.display="none"; } }; xmlLeftAdSpace.send(null); } } function resizeImage(img,containerHeight,containerWidth,centerImage) { img.onload=null; var nimg=new Image(); nimg.onload=function() { if(nimg.height>containerHeight) { var ratioOne=nimg.width/nimg.height; nimg.height=containerHeight; nimg.width=nimg.height*ratioOne; } if(nimg.width>containerWidth) { var ratioTwo=nimg.height/nimg.width; nimg.width=containerWidth; nimg.height=nimg.width*ratioTwo; } img.height=nimg.height; img.width=nimg.width; img.src=nimg.src; if(centerImage==1) { var heightOffset=parseInt((containerHeight-nimg.height)/2); var widthOffset=parseInt((containerWidth-nimg.width)/2); img.style.marginTop=heightOffset+'px'; img.style.marginBottom=heightOffset+'px'; img.style.marginLeft=widthOffset+'px'; img.style.marginRight=widthOffset+'px'; } img.style.display="block"; }; nimg.src=img.src; } function resizeImage2(img,imgURL,containerHeight,containerWidth,centerImage) { img.onload=null; var nimg=new Image(); nimg.onload=function() { if(nimg.height>containerHeight) { var ratioOne=nimg.width/nimg.height; nimg.height=containerHeight; nimg.width=nimg.height*ratioOne; } if(nimg.width>containerWidth) { var ratioTwo=nimg.height/nimg.width; nimg.width=containerWidth; nimg.height=nimg.width*ratioTwo; } img.height=nimg.height; img.width=nimg.width; img.src=nimg.src; if(centerImage==1) { var heightOffset=parseInt((containerHeight-nimg.height)/2); var widthOffset=parseInt((containerWidth-nimg.width)/2); img.style.marginTop=heightOffset+'px'; img.style.marginBottom=heightOffset+'px'; img.style.marginLeft=widthOffset+'px'; img.style.marginRight=widthOffset+'px'; } img.style.display="block"; }; nimg.onerror=function() { img.height=100; img.width=100; img.src='http://images9.green-watch.org/img/png/photoUnavailable.png'; if(centerImage==1) { var heightOffset=parseInt((containerHeight-100)/2); var widthOffset=parseInt((containerWidth-100)/2); img.style.marginTop=heightOffset+'px'; img.style.marginBottom=heightOffset+'px'; img.style.marginLeft=widthOffset+'px'; img.style.marginRight=widthOffset+'px'; } }; nimg.src=imgURL; } function updateSubcatList(scriptParams) { xmlSubcatList=GetXmlHttpObject(); if(xmlSubcatList==null) { return; } url=myDirectory+"/scripts/subcat-list.cfm?search=1&"+scriptParams;xmlSubcatList.open("GET",url,true); xmlSubcatList.onreadystatechange=function() { var availableSubcats=document.getElementById("MY_AVAILABLE_SUBCATS"); if(xmlSubcatList.readyState==4&&xmlSubcatList.responseText!='') { availableSubcats.innerHTML=xmlSubcatList.responseText; availableSubcats.style.display="block"; } else if(xmlSubcatList.readyState==4) { availableSubcats.style.display="none"; } }; xmlSubcatList.send(null); } function updateCountryList(scriptParams) { xmlCountryList=GetXmlHttpObject(); if(xmlCountryList==null) { return; } url=myDirectory+"/scripts/country-list.cfm?search=1&"+scriptParams; xmlCountryList.open("GET",url,true); xmlCountryList.onreadystatechange=function() { var availableCountries=document.getElementById("MY_AVAILABLE_COUNTRIES"); if(xmlCountryList.readyState==4&&xmlCountryList.responseText!='') { availableCountries.innerHTML=xmlCountryList.responseText; availableCountries.style.display="block"; } else if(xmlCountryList.readyState==4) { availableCountries.style.display="none"; } }; xmlCountryList.send(null); } function updateStateList(scriptParams) { xmlStateList=GetXmlHttpObject(); if(xmlStateList==null) { return; } url=myDirectory+"/scripts/state-list.cfm?search=1&"+scriptParams; xmlStateList.open("GET",url,true); xmlStateList.onreadystatechange=function() { var availableRegions=document.getElementById("MY_AVAILABLE_STATES"); if(xmlStateList.readyState==4&&xmlStateList.responseText!='') { availableRegions.innerHTML=xmlStateList.responseText; availableRegions.style.display="block"; } else if(xmlStateList.readyState==4) { availableRegions.style.display="none"; } }; xmlStateList.send(null); } function disableForms() { var inputs=document.getElementsByTagName('input'); for(element in inputs) { if(inputs[element].type=='submit') { inputs[element].disabled=true; } } } document.onfocus=function() { var inputs=document.getElementsByTagName('input'); for(element in inputs) { if(inputs[element].type=='submit') { inputs[element].disabled=false; } } }; function disableElement(element) { var myElement=document.getElementById(element); myElement.disabled=true; } function checkHTML(myElement,myElementError) { xmlCheckHTML=GetXmlHttpObject(); if(xmlCheckHTML==null) { return; } var myElement=document.getElementById(myElement); var myElementError=document.getElementById(myElementError); url=myDirectory+"/scripts/htmlCheck.cfm?myStr="+myElement.value; xmlCheckHTML.open("GET",url,true); xmlCheckHTML.onreadystatechange=function() { if(xmlCheckHTML.readyState==4&&xmlCheckHTML.responseText!='') { myElementError.innerHTML=xmlCheckHTML.responseText; myElementError.style.display="block"; } else if(xmlCheckHTML.readyState==4) { myElementError.style.display="none"; } }; xmlCheckHTML.send(null); } function checkUsername(myElement,myElementError) { xmlCheckUsername=GetXmlHttpObject(); if(xmlCheckUsername==null) { return; } var myElement2=document.getElementById(myElement); var myElementError2=document.getElementById(myElementError); url=myDirectory+"/scripts/checkUsername.cfm?USERNAME="+myElement2.value; xmlCheckUsername.open("GET",url,true); xmlCheckUsername.onreadystatechange=function() { if(xmlCheckUsername.readyState==4&&xmlCheckUsername.responseText!='') { myElementError2.innerHTML=xmlCheckUsername.responseText; myElementError2.style.display="block"; } else if(xmlCheckUsername.readyState==4) { myElementError2.style.display="none"; checkHTML(myElement,myElementError); } }; xmlCheckUsername.send(null); } function checkBizName(newBizName,newBizNameError,curBizName) { xmlCheckBizName=GetXmlHttpObject(); if(xmlCheckBizName==null) { return; } var newBizName2=document.getElementById(newBizName); var newBizNameError2=document.getElementById(newBizNameError); url=myDirectory+"/scripts/checkBizName.cfm?BUSINESS_NAME="+newBizName2.value+"&CURRENT_BIZ_NAME="+curBizName;url=url+"&sid="+Math.random(); xmlCheckBizName.open("GET",url,true); xmlCheckBizName.onreadystatechange=function() { if(xmlCheckBizName.readyState==4&&xmlCheckBizName.responseText!='') { newBizNameError2.innerHTML=xmlCheckBizName.responseText; newBizNameError2.style.display="block"; } else if (xmlCheckBizName.readyState==4) { newBizNameError2.style.display="none"; checkHTML(newBizName,newBizNameError); } }; xmlCheckBizName.send(null); } function checkNumeric(myElement,myElementError) { xmlCheckNumeric=GetXmlHttpObject(); if(xmlCheckNumeric==null) { return; } var myElement2=document.getElementById(myElement); var myElementError2=document.getElementById(myElementError); url=myDirectory+"/scripts/numericCheck.cfm?myStr="+myElement2.value; xmlCheckNumeric.open("GET",url,true); xmlCheckNumeric.onreadystatechange=function() { if(xmlCheckNumeric.readyState==4&&xmlCheckNumeric.responseText!='') { myElementError2.innerHTML=xmlCheckNumeric.responseText; myElementError2.style.display="block"; } else if(xmlCheckNumeric.readyState==4) { myElementError2.style.display="none"; } }; xmlCheckNumeric.send(null); } function checkWebsite(myElement,myElementError) { xmlCheckWebsite=GetXmlHttpObject(); if(xmlCheckWebsite==null) { return; } var websiteURL=document.getElementById(myElement); var websiteError=document.getElementById(myElementError); url=myDirectory+"/scripts/checkWebsite.cfm?WEBSITE_URL="+websiteURL.value; xmlCheckWebsite.open("GET",url,true); xmlCheckWebsite.onreadystatechange=function() { if(xmlCheckWebsite.readyState==4&&xmlCheckWebsite.responseText!='') { var errorMessage = 'That website URL is not valid. If you type your website '; errorMessage+='URL as plain text, we will take care of the rest. Error: '+xmlCheckWebsite.responseText+''; websiteError.innerHTML=errorMessage; websiteError.style.display="block"; } else if(xmlCheckWebsite.readyState==4) { websiteError.style.display = "none"; } }; xmlCheckWebsite.send(null); } function checkCityName(regionElement,cityElement,cityElementError) { xmlCheckCity=GetXmlHttpObject(); if(xmlCheckCity==null) { return; } var cityName=document.getElementById(cityElement); var regionID=document.getElementById(regionElement); var cityNameError=document.getElementById(cityElementError); url=myDirectory+"/scripts/checkCityName.cfm?CITY_NAME="+cityName.value;url=url+"&STATE_ID="+regionID.value; xmlCheckCity.open("GET",url,true); xmlCheckCity.onreadystatechange=function() { if(xmlCheckCity.readyState==4&&xmlCheckCity.responseText!='') { cityNameError.innerHTML=xmlCheckCity.responseText; cityNameError.style.display="block"; } else if(xmlCheckCity.readyState==4) { cityNameError.style.display="none"; } }; xmlCheckCity.send(null); } function charCheck(myLabel,myTxtArea,charCount) { var charsRemainingLabel=document.getElementById(myLabel); var myTextArea=document.getElementById(myTxtArea); var charsLeft=charCount-myTextArea.value.length; if(charsLeft < 0) { alert("You can only enter up to "+charCount+" characters for this text field."); myTextArea.value=myTextArea.value.substring(0,charCount); charsRemainingLabel.innerHTML="0 characters remaining"; } else if(charsLeft==1) { charsRemainingLabel.innerHTML=charsLeft+" character remaining"; } else { charsRemainingLabel.innerHTML=charsLeft+" characters remaining"; } }
// ]]>
</script>

All resources on the page were exactly the same so the only change was the two code blocks above. The code attached in Exhibit A matches the code block in Exhibit B.

Test Results:

Exhibit A

http://www.webpagetest.org/result/100709_1445/
http://www.webpagetest.org/result/100709_144E/

Exhibit B

http://www.webpagetest.org/result/100709_143X/
http://www.webpagetest.org/result/100709_1443/

The test results showed that the inline JavaScript in Exhibit B resulted in a faster page speed both first view and repeat view.

I figure the difference in execution time would be the time to first byte for the included JavaScript. In Exhibit B, you do not have that look up time.

The blocking of JavaScript is due to the execution of the JavaScript and not necessarily how it is included correct?

If you have to include a script that blocks other downloads, why not include it inline as opposed to fetching it with src?

Some people may argue that code changes are easier to update. However, with dynamic scripting languages such as coldfusion, you can just include a template that will allow JavaScript changes in one location.

How would having the inline JavaScript at the top effect website crawlers such as Google? Would Google look past all the JavaScript?

Any thoughts or comments?

Sincerely,
Travis Walters[hr]

One problem with inlining is the increased page weight per page when looking across multiple pages on your site.
Having JS/CSS external allows the browser to cache JS/CSS that might be used across multiple pages. Using the inlining technique you pay the page weight penalty each time.
If the JS/CSS is specific only to that page then you have a valid argument for inlining it.
If you are serving up content to mobile users then it is also worth paying the page weight penalty as having to get many resources for a page is expensive over mobile connections.
If there is high latency, high bandwidth then inlining is best, as in mobile. If there is low latency, high bandwidth then a degree of parallelism will win over downloading one large resource.
You ask a question about “if you have to include a script that blocks other downloads”…I would ask back does it really have to block downloads? If so would it not be worth investigating redesigning the page so that it does not rely on the blocking javascript. You may be able to move to a more asynchronous model that gives a perceived performance improvement in terms of the end user experience even though the overall performance may not be improved.
Looking at your tests the results times are very similar. So similar on the repeat views section that I would consider them to be the same. It is important to take into account the margin of error produced in the results from the test platform. Whilst I don’t know the margin of error for WebPagetest i would suggest that the results for the repeat views are likely to fall within it and therefore can be considered to be the same. Maybe a suggestion for aiding in the analysis of results would be the publication,by WebPagetest, of the likely margin of error for the test run?
From the looks of the js ( i had a v.brief look) you have inlined you could asynchronously load that resource using a pattern like the latest one used by google for google analytics. This should reduce the time to document complete further for your tests.

I see no reason as to why Google would be affected by the inline javascript, they heavily use inlining with mobile applications. The performance indicator they are taking into account for their ranking is the page load time (I think it is to the Onload event but I’m not sure), so if the inlined page is quicker then, all things being equal between yourself and your competitors in the other aspects of ranking, you should see your ranking improve.

Cheers
Cal

The script I used for the experiment is 15 KB in size so it does not add -much- weight across the website but it does add some to the main document for repeat views.

This script is mainly functions that are used on most webpages across the site and includes functions to import other JavaScript asynchronously. When I first came to webpagetest.org, I had over 200KB of JavaScript that blocked other resources from being downloaded.

I believe in this case the negative page weight penalty is outweighed by having one less resource to download, the ability to download an additional resource in parallel, and the deduction of the time to first byte for the external JavaScript file that is being inlined.

Mobile users are not a concern at this point. I have little experience designing applications for mobile users unfortunately but perhaps in the future I will consider this.

On a side note, I also saw a bit of a difference when I got rid of white space, but that was not involved in this particular experiment.

The results seem to show that in the experiment you are testing on WebPagetest that the external resource is taking a shorter time than the inlined resource, so it might be possible to surmise that the costs of inlining are outweighing the costs of an external extra resource, and are not in line with what you believe (if I have correctly understood your position). This is going by the First view Load time metric as the other metrics are so similar, TTFB only has a spread of 31 ms for the repeat view and 56 ms for the first view with no apparent separation between the result groups for A and B. The First View Load Time only has a spread of 159ms with a separation between the results groups of 73 ms (between highest result for A and lowest result for B).

What is the most important metric that you want to improve with the experiment?

My apologies. I had a typo in my original post.

It was suppose to be:

The test results showed that the inline JavaScript in Exhibit B resulted in a faster page speed both first view and repeat view.

I changed it above now so other people do not get confused. Sorry about that.

I’ve just noticed that I can’t see the siteJavascript.cfm resource on either of the results pages for Exhibit A (JS as external)…in fact it turns up in Exhibit B (JS as inline)…so I think the results pages are under the wrong exhibits, which has also led to some confusion.

So are we looking at the Fully loaded time metric or the Document complete metric? I would argue that the Document Complete metric is more valid in this situation.
The external JS is being downloaded in parallel with an external CSS from your cdn subdomain, in fact it takes less time to download than the css file. You are downloading images from sharded image subdomains in parallel to these 2 resources being served from the cdn subdomains. So I am not seeing any blocking behaviour in particular. I think the differences in the times can be related more strongly to a variance in the response time of your server rather than a variance in the behaviour of the inlined/external JS.

Also you are using the same IP for a number of your domains (CDN and images), you could try the nested CNAMEs DNS hack to cut down the number of DNS requests for colocated domains from 4 to 1.

Hey There,

You were correct that the exhibit results were labeled incorrectly. That is probably why I labeled the sentence under it wrong to begin with.

I edited the initial post once again to switch the results labels.

I have been focusing on getting my document complete a bit faster because this is what Google measures page speed by (at least what I have heard).

However, I still keep an eye on fully loaded as I want to increase user experience as well.

When I had the 200KB JavaScript files, that is when I had a lot of blocking. The functions that are there now are mainly to initialize asynchronous downloads.

Could you tell me more about the CNAME DNS hack you are referring to?

Currently I have multiple subdomains setup using CNAMEs that point to the maxcdn service.

Sincerely,
Travis Walters

The CNAME DNS hack is a pretty cool trick that was discussed at velocity this year where instead of the usual:

name1 → hostname → IP
name2 → hostname → IP
name3 → hostname → IP

where all 3 sharded sub-domains are CNAMES to the same host name (or A records to an IP) you do:

name1 → name2 → name3 → hostname → IP

and have the CNAMES point to each other in a chain. That way when the lookup is done for name1 you will get name2 and name3 in the same result.

It’s really easy to pull off if you’re not using a CDN and the shards are pointing to the same IP as your base page. In the case of a CDN you just need to put your initial CSS and JS on name1 and any images can be spread across them (in order to get the benefit of name1 having resolved 2 and 3).

So basically this would only cause one DNS request correct?

So basically I could have images.green-watch.org, images2.green-watch.org, images3.green-watch.org, etc
download resources in parallel with just one DNS request.

I would just need to make sure resources are always served from the same subdomain to increase cache potential or
would that matter with the hack because it would point back to the cdn before resolving?

Yes, just one DNS request (assuming you can have the chain resolved before it would try to fetch the images, otherwise it will kick off the 3 DNS lookups in parallel anyway) and yes, you still need to keep resources on the same domains for the browser cache to recognize them.

Hey Patrick,

Can you tell me if I did this correctly?

I have CNAMEs setup using this format:
ALIAS NAME → FULLY QUALIFIED DOMAIN NAME (FQDN) FOR TARGET HOST

images1 → images2.green-watch.org.
images2 → images3.green-watch.org.
images3 → images4.green-watch.org.
images4 → images5.green-watch.org.
images5 → images6.green-watch.org.
images6 → images7.green-watch.org.
images7 → images8.green-watch.org.
images8 → images9.green-watch.org.
images9 → images10.green-watch.org.
images10 → cdn.green-watch.org
cdn → MYSUBDOMAIN.netdna-cdn.com.

The MYSUBDOMAIN stands for the subdomain maxcdn gave me.

I just did this last night so perhaps it has not propagated yet because I have not seen a difference in results.

Thanks again for any information. :slight_smile:

Sincerely,
Travis Walters

Short version: I think it’s a testing problem and you have it set up correctly.

Long version:

Looks like you have it configured correctly:

Non-authoritative answer:
Name: cdn.greenwatchllc.netdna-cdn.com
Address: 69.174.57.105
Aliases: images1.green-watch.org
images2.green-watch.org
images3.green-watch.org
images4.green-watch.org
images5.green-watch.org
images6.green-watch.org
images7.green-watch.org
images8.green-watch.org
images9.green-watch.org
images10.green-watch.org
cdn.green-watch.org

There’s a really good chance that I need to tweak the testers a bit for the benefit to show. Right now I have the windows DNS caching service disabled so that DNS lookups will not get cached across multiple browsers and from run to run.

I’ll take a look and see what it would take to flush the DNS cache between runs instead (this wouldn’t have worked with multiple browsers running at the same time but now that I’m down to a single browser per machine it may be feasible).

Thanks,

-Pat

I see that Pat got there before me :slight_smile:

The only suggestion I would add would be to have the entry point to the alias stack be the first DNS request that will be called from your page. So it is worth having your CSS/JS files served from the same subdomain.

So in this instance you serve your CSS/JS files from cdn.green-watch.org, so I would configure the stack like so:

cdn → images1.green-watch.org.
images1 → images2.green-watch.org.
images2 → images3.green-watch.org.
images3 → images4.green-watch.org.
images4 → images5.green-watch.org.
images5 → images6.green-watch.org.
images6 → images7.green-watch.org.
images7 → images8.green-watch.org.
images8 → images9.green-watch.org.
images9 → images10.green-watch.org.
images10 → MYSUBDOMAIN.netdna-cdn.com.

This way the first DNS request to cdn should logically force the recovery of all the aliases. I’m going to have to do some digging to confirm thats actually how the CNAME lookup will work.

With browsers that have more that 2 parallel connections and no blocking JS then any images in the main page might end up causing further DNS requests if served from different subdomains. But through some planning it should be possible to migitate this.

On the subject of reducing your document Complete time, I had a brief look at your test site http://www.green-watch.org/test4.cfm. It appears that you are resizing a number of your images in html/css. The elements that look to be pushing out your document complete time are images. These seem to be limited by transfer speeds, 900ms to transfer 30KB of image (the sears coupon). So making sure you serve up images that are the same size as required by your page will improve matters in this regard. Having said that the page is much faster on my home DSL connection only 169ms for the 30KB image which is more in line with a 2Mb/s DSL link (if my calculations aren’t skewed). If these images are being served from a CDN, this download time seems a little slow.

Also are these coupons integral to the initial content of the page? Might it be worth asynchronously loading them and progressively enhancing your page with the graphics. This way you can kinda cheat on your document complete time, howver you will need to make sure that the site looks good in a “bare” state which then gets enhanced with the extra widgets and goodies.

Cheers
Cal

Thanks for all the great advice. I just changed the cdn alias so it is first in the list and the images10 alias so it is last on the list. I guess I just have to wait until they propagate. In regards to images, I have to import about 45 million more products, download images, resize them, etc. Now would be a great time to look back into those scripts since the layout is messed up until it propagates. The coupons on the home page (and load sequence) are temporary right now. Green coupons is something I am going to be implementing on my website eventually. So much to do :slight_smile:

Hey Guys,

Do either of these links work for you:

#1 - http://cdn.green-watch.org/inc/style.cfm
#2 - http://images1.green-watch.org/img/jpg/myLogo2.jpg

These are towards the end of my CNAME chain and do not work for me at this time.

I was looking at the following post: http://fixunix.com/dns/51757-chaining-cnames.html

“To prevent loops, servers usually have a limit on the number of times they’ll restart a query. And since it’s not common to have long chains of CNAMEs, the limit is typically pretty low, like 5-10, and most of this can be taken up by having to resolve NS records in delegations.”

Does anybody know if there is such a limit to the number of CNAMEs that can be chained together?

Also Patrick, I seen the following article: How to Flush DNS - Tech-FAQ

Perhaps you could use the “ipconfig /flushdns” run command to flush the DNS settings some how? On browser close, run a batch file with that command in it?

Sincerely,
Travis Walters

#1 works for me but #2 returns a 502 error (not a DNS problem). If there is ANY risk I’d err on the side of much shorter chains.

in the DNS flush front, I’m checking quickly to see if I can find an API first. If not then I’ll launch a shell with ipconfig /flushdns. I need to make a few other tweaks to the code to not disable the caching service as well. Probably be out at the pool with the kids today so I’ll try to wedge it in tonight or tomorrow.

I just noticed that under maxcdn the original IP address is set to 69.174.57.105 for images1.green-watch.org and 69.43.203.167 for the other subdomains. When I click the test IP button I get a bad gateway error which is a 502 error like you stated. I made a Host (A) record for images1 just now that points to 69.43.203.167. I think once it resolves I can update it through maxcdn and then try to create the chain again. I am about to watch the soccer game finals and call it a night or day rather :slight_smile:

From my ISP this wont work, but if I were set to using Google’s DNS or OpenDNS (i assume both to follow the RFCs to the book) it would have worked

Both give me server not found err on firefox… My ISP probably has limits on the size of the chain. (note the missing A record when querying my default nameserver)

Using my ISP

[code]sajal@sajal-laptop:~$ dig cdn.green-watch.org

; <<>> DiG 9.7.0-P1 <<>> cdn.green-watch.org
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26905
;; flags: qr rd; QUERY: 1, ANSWER: 8, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;cdn.green-watch.org. IN A

;; ANSWER SECTION:
cdn.green-watch.org. 3124 IN CNAME images1.green-watch.org.
images1.green-watch.org. 3124 IN CNAME images2.green-watch.org.
images2.green-watch.org. 3124 IN CNAME images3.green-watch.org.
images3.green-watch.org. 3124 IN CNAME images4.green-watch.org.
images4.green-watch.org. 3124 IN CNAME images5.green-watch.org.
images5.green-watch.org. 3124 IN CNAME images6.green-watch.org.
images6.green-watch.org. 3124 IN CNAME images7.green-watch.org.
images7.green-watch.org. 3124 IN CNAME images8.green-watch.org.

;; Query time: 21 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: Mon Jul 12 05:01:19 2010
;; MSG SIZE rcvd: 333

sajal@sajal-laptop:~$
[/code]

Using Google’s DNS

[code]sajal@sajal-laptop:~$ dig cdn.green-watch.org @8.8.8.8

; <<>> DiG 9.7.0-P1 <<>> cdn.green-watch.org @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38986
;; flags: qr rd ra; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;cdn.green-watch.org. IN A

;; ANSWER SECTION:
cdn.green-watch.org. 3460 IN CNAME images1.green-watch.org.
images1.green-watch.org. 3460 IN CNAME images2.green-watch.org.
images2.green-watch.org. 3460 IN CNAME images3.green-watch.org.
images3.green-watch.org. 3460 IN CNAME images4.green-watch.org.
images4.green-watch.org. 3460 IN CNAME images5.green-watch.org.
images5.green-watch.org. 3460 IN CNAME images6.green-watch.org.
images6.green-watch.org. 3460 IN CNAME images7.green-watch.org.
images7.green-watch.org. 3460 IN CNAME images8.green-watch.org.
images8.green-watch.org. 3460 IN CNAME images9.green-watch.org.
images9.green-watch.org. 3460 IN CNAME images10.green-watch.org.
images10.green-watch.org. 3460 IN CNAME images10.greenwatchllc.netdna-cdn.com.
images10.greenwatchllc.netdna-cdn.com. 160 IN A 69.174.57.105

;; Query time: 47 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Mon Jul 12 05:01:44 2010
;; MSG SIZE rcvd: 325

sajal@sajal-laptop:~$
[/code]

Using OpenDNS

[code]sajal@sajal-laptop:~$ dig cdn.green-watch.org @208.67.222.222

; <<>> DiG 9.7.0-P1 <<>> cdn.green-watch.org @208.67.222.222
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44648
;; flags: qr rd ra; QUERY: 1, ANSWER: 12, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;cdn.green-watch.org. IN A

;; ANSWER SECTION:
cdn.green-watch.org. 3600 IN CNAME images1.green-watch.org.
images1.green-watch.org. 3600 IN CNAME images2.green-watch.org.
images2.green-watch.org. 3600 IN CNAME images3.green-watch.org.
images3.green-watch.org. 3600 IN CNAME images4.green-watch.org.
images4.green-watch.org. 3600 IN CNAME images5.green-watch.org.
images5.green-watch.org. 3600 IN CNAME images6.green-watch.org.
images6.green-watch.org. 3600 IN CNAME images7.green-watch.org.
images7.green-watch.org. 3600 IN CNAME images8.green-watch.org.
images8.green-watch.org. 3600 IN CNAME images9.green-watch.org.
images9.green-watch.org. 3600 IN CNAME images10.green-watch.org.
images10.green-watch.org. 3600 IN CNAME images10.greenwatchllc.netdna-cdn.com.
images10.greenwatchllc.netdna-cdn.com. 300 IN A 69.174.57.105

;; Query time: 599 msec
;; SERVER: 208.67.222.222#53(208.67.222.222)
;; WHEN: Mon Jul 12 05:02:18 2010
;; MSG SIZE rcvd: 325

sajal@sajal-laptop:~$
[/code]

Hey Guys,

Thanks for the responses. From what I have gathered, it looks like there is a maximum chain length. From Sajal’s results, it looks like it may vary by ISP or DNS server. Before I give up on chaining CNAMEs together, I am going to try to create three smaller chains like so:

images1 → images2.green-watch.org.
images2 → images3.green-watch.org.
images3 → images3.greenwatchllc.netdna-cdn.com. → 69.174.57.105

images4 → images5.green-watch.org.
images5 → images6.green-watch.org.
images6 → images7.green-watch.org.
images7 → images7.greenwatchllc.netdna-cdn.com. → 69.174.57.105

cdn → images8.green-watch.org.
images8 → images9.green-watch.org.
images9 → images10.green-watch.org.
images10 → images10.greenwatchllc.netdna-cdn.com → 69.174.57.105

Sincerely,
Travis Walters

Couple of things:

  1. Another downside of in-lining JS is that most bots will only crawl a certain distance into the page, and they weight the content at the top more heavily than the content at the bottom. By putting inline JS in your document head you may make the bot bail before it finishes crawling the whole page, and you may reduce the weight of any real content you have in the body of your document.

  2. Sharding static content across 10 sub-domains is probably overkill. Depending on what browsers people are typically using on your site this could cause way too much thrashing. A browser like Firefox that makes 8 connections per domain doesn’t need that level of sharding (in fact it will likely make your site load SLOWER). The recommendation from Yahoo is to shard across two domains and no more. That post is also old, written when many more people were using IE 6 and 7 and we needed to be sharding more aggressively.