1. Meetup discussed optimizing the performance of their event details page by externalizing scripts, moving scripts to the bottom of the page, reducing requests, lazy loading scripts, and using event delegation.
2. These changes reduced the page load time by 27% from 6.321 seconds to 4.643 seconds and reduced javascript requests by 50%.
3. Meetup also optimized static content serving by versioning, compressing, caching resources and using a CDN to improve performance further.
1. Meetup Performance
Greg Whalin, CTO Meetup (@gwhalin), Justin Cataldo, Lead UI Engineer (@jcataldo), Will Howard, Lead UI Engineer
2. Meetup
Platform for local groups
Mission is MEME (Meetup Everywhere About Most Everything)
~6.2m members
~70k Groups
~500k group joins every month
~5 million Meetups have happened
~53 million RSVPs
3. General Architecture and Back-
end Performance
(just a tiny bit - this could and
should be another presentation)
4. Data
MySQL (any RDBMs store) biggest pain
replication
use InnoDB!
smart indexing (take advantage of clustered indexes)
server side conn pools and short timeouts on connections
smart about fs choice on Linux (we use XFS after benchmarking)
Hardware - relatively expensive boxes
multi-core (8 or 16) Opteron
lots of ram (32/64GB)
fast drives (lots of spindles, RAID10)
Cache, cache, cache!
innodb buffer cache
memcache
local app server memory
Shrink data when possible
archive unused data
custom serialization when serializing
data partitioning/sharding
5. Storage
Over half a million photos uploaded to Meetup every month
Scaled and processed into 4 different sizes (plus original)
6. Storage solutions
Options for growth include NAS, SAN, or something else
NAS and SAN are single point of failure and possibly $$$
Only postpones problem
7. MogileFS
developed by Brand Fitzpatrick (i.e. Memcached)
OSS distributed filesystem (built in Perl)
any hard drive on network can easily be added to cluster
scales easily and cheaply
13. Why does performance matter?
Slow site
Bad User Experience
Drop in Member
Activity
14. Why focus on front end performance?
Back end only accounts for 10-15% of the response time
Less time and resources
Costs less
http://developer.yahoo.net/blog/archives/2007/03/high_performanc.html
19. 3 Steps to improving performance
1. Externalize script
2. Move scripts to the bottom of the page
3. Reduce requests
20. 3 Steps to improving performance
1. Externalize script
2. Move scripts to the bottom of the page
3. Reduce requests
21. Why externalize scripts
Prevents blocking
Inline scripts prevent asynchronous downloads
Downloads must wait for the script to be executed
Caching
Inline JavaScript is downloaded every time
External scripts are cached by the browser
Reduced overall page size
Reusable
Can use the same code somewhere else on the site
easily
23. 3 Steps to improving performance
1. Externalize script
2. Move scripts to the bottom of the page
3. Reduce requests
24. Web:script
Custom tag built in house
Moves inline and external script to the bottom of the page
Allows UI engineers to not have to worry about where they
place scripts
Compresses inline script using YUICompressor
/***** Load External Script *****/
<web:script src="/script/Meetup/packed/EventDetails.js" />
/***** Load Inline Script *****/
<web:script>
Meetup.Copy.noMembersMarkedAttended = "<trn:message key="event.attendance.noMembersMarkedAttended">No
members have been marked attended</trn:message>";
Meetup.Copy.noMembersMarkedAttendedDynam = '<trn:message key="event.attendance.
noMembersMarkedAttendedDynam"><trn:param name="GROUPING">__GROUPING__</trn:param>No members in
"{GROUPING}" have been marked attended</trn:message>';
Meetup.Copy.noMembersMarkedAbsent = "<trn:message key="event.attendance.noMembersMarkedAbsent">No
members have been marked absent</trn:message>";
</web:script>
25. 3 Steps to improving performance
1. Externalize script
2. Move scripts to the bottom of the page
3. Reduce requests
27. Concatenation using Sprockets
Sprockets (www.getsprockets.com)
Created by 37Signals
Ruby library that preprocesses and concatenates JavaScript
files
Baked into our build process
30. Lazy Loading
Defer loading of javascript files until they are needed
Reduces the initial upfront requests
Helps reduce blocking by downloading files asynchronously
Precaching
31. Lazy Loading: How it works
Inserts scripts into the head dynamically
var scriptNode = function(src) {
return createDOM("script", {
"type": "text/javascript",
"src": src
});
}
var load = function(id, src, n) {
var script = scriptNode(url);
head.appendChild(script);
script.onload = script.onreadystatechange = function() {
if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") {
script.onload = script.onreadystatechange = null;
}
}
}
Meetup.Script.include("http://static2.meetupstatic.com/script/Meetup/DomDeco/LinkDecorator.js",callback);
38. Execute early: DOMReady
Libraries that have a DOM ready solution:
jQuery
YUI
Prototype
Pretty much every modern JS library (not MochiKit)
Meetup uses MochiKit, so we rolled our own.
39. Execute early: DOMReady
And by rolled our own, I mean we're using the Dean
Edwards/Matthias Miller/John Resig implementation.
http://dean.edwards.name/weblog/2006/06/again/#comment5338
With a few changes.
Meetup.DOMReady.ready(function(){
Meetup.EventDetails.init();
if(Meetup.EventDetails.isCanceled != 4 && Meetup.EventDetails.rsvp != 0){
deletePopup = new Meetup.CommentDeleteConfirm();
deletePopup.pagerOffsetFieldName = "p_commentsList";
deletePopup._decorate();
}
});
40. Execute early: Even earlier
Do you need to wait for the DOM to be ready?
If you aren't manipulating the DOM, there's no reason to
wait until it's ready.
41. Automated Image Optimization
Using smush.it
http://developer.yahoo.com/yslow/smushit/
Smusher Ruby gem
http://github.com/grosser/smusher (gem install smusher)
BASH script that watches our image directories for
changes and executes smusher.
./filescan.sh /usr/local/meetup/static/img/ 'smusher -q @' 'jpg,png'&
43. Event Delegation
But first, a little bit about event bubbling...
From: http://www.quirksmode.org/js/events_order.html
44. Event Delegation
Pros Cons
Does not work well with nested elements
Much faster on load (not
Doesn't work with all events
connecting DOM elements)
Slight performance hit with execution
No need to disconnect /
reconnect with AJAX calls
Fewer memory leaks
A lot of JS libraries already have plug-ins for event
delegation (jQuery, YUI, prototype).
But, it's pretty easy to write your own (we did).
47. Event Delegation: Meetup.Dispatcher
<div id="C_page">
...
<span class="meetup-topic"><a class="topic-id-7029 topic-link J_onClick topic-info-hover" href="
http://javascript.meetup.com/cities/us/ny/brooklyn/">JavaScript</a></span>
...
</div>
// Inits a new instance of dispatcher
// Connects a mouseover event to the parent container "C_page"
var mdp = Meetup.Dispatcher.init("C_page", "onmouseover");
// Calls Meetup.UI.infoHover.mouseOver() when target element has "topic-info-hover" class.
mdp.registerFunc("topic-info-hover", Meetup.UI.InfoHover.mouseOver);
Meetup.UI.infoHover.mouseOver = function(e) {
topicId = _getTopicId(e.target());
if (!topicId || topicId == "") return;
_primeCache(topicId);
var activeEl = _getActiveEl(e.target());
var pos = getElementPosition(activeEl);
...
}
48. Speeding up DOM crawling with Sizzle
sizzlejs.com
Internet Explorer 7
MochiKit: 6623.94ms
Sizzle: 306.03ms
Firefox 3.5
MochiKit: 210.524ms
Sizzle: 111.553ms
49. Where do we go from here?
More concatenation and lazy loading where it makes sense
Defer image loading where it makes sense
Reduce DOM elements
Reduce CSS and improved selector efficiency
and more
51. Launches
Launch multiple times a day (sometimes)
Need launches to be quick / no downtime
Optimize static resources only at deploy time and only if
modified
52. Deployment of static content
Sprockets (reduce requests)
YUICompressor for js (local mod to speed up optimizing
multiple files)
Pre-compress css and jsp
Set cache-control to be fresh for over a year (indefinite)
All links on site generate programatically and versioned