Earning An "A" In YSlow – SDRuby 2009-03-05 - Presentation Transcript
GETTING AN “A” IN YSLOW
Web Performance Optimization 101
Nick Zadrozny <nick@beyondthepath.com>
Friday, March 6, 2009
HI, MY NAME IS NICK.
I MAKE WEBSITES.
Friday, March 6, 2009
I’M EMBARRASSED
MY WEBSITES ARE SLOW
Friday, March 6, 2009
<METAPHOR>
Friday, March 6, 2009
You’re driving a brand new car
It’s moving really slowly
You can smell smoke
People are giving you looks
Friday, March 6, 2009
You’re driving a brand new car
It’s moving really slowly Return the car
You can smell smoke Rebuild the car
People are giving you looks Disengage the hand brake
Friday, March 6, 2009
D’oh!
Friday, March 6, 2009
</METAPHOR>
Friday, March 6, 2009
THE POINT
LOW-HANGING FRUIT…
Friday, March 6, 2009
YAHOO!
THEY MAKE WEBSITES
Friday, March 6, 2009
Yahoo!
34 “Best Practices” for Exceptional Performance
1. Minimize HTTP 9. Reduce DNS Lookups 19. Reduce the Number of 27. Choose <link> over
Requests DOM Elements @import
10. Minify JavaScript and
2. Use a Content CSS 20. Split Components 28. Avoid Filters
Delivery Network Across Domains
11. Avoid Redirects 29. Optimize Images
3. Add an Expires or a 21. Minimize the Number
12. Remove Duplicate 30. Optimize CSS Sprites
Cache-Control of iframes
Scripts
Header 31. Don’t Scale Images in
22. No 404s
13. Configure ETags HTML
4. Gzip Components
23. Reduce Cookie Size
14. Make Ajax Cacheable 32. Make favicon.ico
5. Put Stylesheets at the
24. Use Cookie-free Small and Cacheable
Top 15. Flush the Buffer Early Domains for
33. Keep Components
6. Put Scripts at the Components
16. Use GET for AJAX under 25K (mobile)
Bottom Requests 25. Minimize DOM
34. Pack Components
7. Avoid CSS Access
17. Post-load into a Multipart
Expressions Components 26. Develop Smart Event Document (mobile)
8. Make Javascript and Handlers
18. Preload Components
CSS External
Friday, March 6, 2009
YSLOW
PLUGIN FOR FIREBUG
Friday, March 6, 2009
YSlow
Tests Your Site on 13 Rules
Minimize HTTP Put Stylesheets at Reduce DNS
Requests the Top Lookups
Use a Content Put Scripts at the Minify JavaScript
Delivery Network Bottom and CSS
Add an Expires Avoid CSS Avoid Redirects
or a Cache- Expressions
Remove
Control Header
Make Javascript Duplicate Scripts
Gzip and CSS External
Configure ETags
Components
Friday, March 6, 2009
MINIMIZE HTTP REQUESTS
Friday, March 6, 2009
Minimize HTTP Requests
HTTP Requests are Expensive!
Net work latency + server latency + net work latency + download time.
This rule is fundamental, the Catch-All. Everything else is based on it.
Asset packaging reduces overall number of requests.
Compression & minification reduce the size of responses.
Reordering includes lets important stuff go first.
ETags and Last-Modified checks reduce the content of an HTTP Request.
Browser caching completely eliminates subsequent requests. Major win.
Friday, March 6, 2009
MAKE JAVASCRIPT
AND CSS EXTERNAL
Friday, March 6, 2009
Make Javascript
and CSS External
Easier to maintain, less HTML to send
Browser caching prevents later requests
Help order requests by priority
Rails makes this pretty easy,
stylesheet_link_tag
javascript_include_tag
Friday, March 6, 2009
MINIFY JAVASCRIPT
AND CSS
Friday, March 6, 2009
Minify Javascript and CSS
≈20% size reduction html,body,div,span,applet,object,iframe,h1,h2
,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,
address,big,cite,code,del,dfn,em,font,img,ins
Using a framework? You’re ,kbd,q,s,samp,small,strike,strong,sub,sup,tt,
var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,f
orm,label,legend,table,caption,tbody,tfoot,th
probably halfway there. ead,tr,th,td{margin:0;padding:0;border:
0;outline:0;font-size:100%;vertical-
align:baseline;background:transparent}
body{line-height:1}ol,ul{list-style:none}
YUI Compressor + Rake blockquote,q{quotes:none}blockquote:before,bl
ockquote:after,q:before,q:after{content:'';co
ntent:none}:focus{outline:0}ins{text-
decoration:none}del{text-decoration:line-
through}table{border-collapse:collapse;
border-spacing:0}
Friday, March 6, 2009
STYLESHEETS AT THE TOP
SCRIPTS AT THE BOTTOM
Friday, March 6, 2009
Stylesheets at the top
Scripts at the bottom
<!DOCTYPE html>
Javascript shouldn’t block <html>
<head>
more important rendering
<title>Your Awesome Site</title>
<%= stylesheet_link_tag 'application' %>
<%= yield(:head) %>
</head>
<body>
Pass blocks to the head and <div id=\"page\">
<header>
foot from within your views: <h1>Awesome Site</h1>
</header>
<article>
<%= yield %>
<% content_for(:foot) do %> </article>
<%= javascript_tag \"…\" %> </div>
<%= javascript_include_tag 'application' %>
<% end %> <%= yield(:foot) %>
</body>
</html>
Friday, March 6, 2009
GZIP COMPONENTS
Friday, March 6, 2009
GZip Components
Another nice size reduction
Most devices support it
CSS, Javascript, text/html,
text/plain
Friday, March 6, 2009
SOME REALLY EASY ONES…
Friday, March 6, 2009
AVOID CSS EXPRESSIONS
Friday, March 6, 2009
AVOID REDIRECTS
Friday, March 6, 2009
REMOVE DUPLICATE
SCRIPTS
Friday, March 6, 2009
REDUCE DNS LOOKUPS
Friday, March 6, 2009
Apache gives this for free for most of your
assets.
Rails gives this for free for all of the
responses it sends back. It also has some
helpers that will let you potentially skip db
calls and expensive view rendering.
CONFIGURE ETAGS
Friday, March 6, 2009
Configure ETags
Web servers likely give you a lot for free
Rails gives you some ETagging for free
Rails ETag and Last-Modified helper
def show
@article = Article.find(params[:id])
if stale?(:etag => @article, :last_modified => @article.created_at.utc)
@statistics = @article.really_expensive_call
respond_to do |format|
# etc
end
end
end
Friday, March 6, 2009
ADD AN EXPIRES OR
CACHE-CONTROL HEADER
Friday, March 6, 2009
Add an Expires Header
Check out mod_expires for Apache
Rails already does “soft” cache busting
Hard cache busting (for CDNs)…
config.action_controller.asset_host =
\"http://assets.example.com/#{REVISION}\"
Friday, March 6, 2009
USE A CDN
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: set the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/environment'
require 'aws/s3'
require 'progressbar'
# Set up the S3 configuration and connection
config = YAML.load(File.open(File.join(RAILS_ROOT, 'config/amazon_s3.yml')))[RAILS_ENV]
bucket = config['bucket_name']
AWS::S3::Base.establish_connection!(
:access_key_id => config['access_key_id'],
:secret_access_key => config['secret_access_key']
)
# Grab everything in the public directory
files = Dir.glob(File.join(RAILS_ROOT, 'public/**/*/**/*.{css,js,gif,jpg,png,…}'))
# Set up the progressbar
progressbar = ProgressBar.new(\"Uploading...\", files.size)
# Iterate through the files
files.each do |file|
# Skip directories themselves - we're only interested in files
unless File.directory?(file)
# Create an object key from the file name and revision
key = file.gsub(/.*public\\//,\"#{REVISION}/\")
# Upload the object to S3
# TODO: make sure we're setting the right headers for gzipped objects
AWS::S3::S3Object.store(
key, open(file), bucket,
:access => :public_read, :expires => 1.year.from_now
)
# Increment the progressbar
progressbar.inc
end
end
Friday, March 6, 2009
THAT’S IT!
Friday, March 6, 2009
Friday, March 6, 2009
ARTICLE FORTHCOMING
BEYOND THE PATH.COM
Friday, March 6, 2009
0 comments
Post a comment