Sie sind auf Seite 1von 166

When to tell your kids

about presentation
caching

Matthew Deiters
www.theAgileDeveloper.com
A practical guide to
stuffing your app’s bits
into someone else’s
browser
Questions: @mdeiters
Rapid Feature
Development
Rapid Feature Adoption &
Development Growth
Client Caching
client.is_a?(Browser) == true
Browsers &
Leveraging HTTP 1.1
Fewer Requests

Smaller Responses
80/20 Rule
(Pareto Principle)
80% of the wealth owned by 20% of people
80% of your time is with 20% of your acquaintances
80% of the time you wear 20% of your clothing
80% of a request is spent on the wire
Today
Reducing Network Traffic
Last-Modified Header

ETag Header
GZip

Today
Minification

max-age Header

Cookies

Expires Header
ME
To illustrate:
Scalability
Applicable for?
Applicable for?

Enterprises
Applicable for?

Enterprises

High Traffic Web Sites


Applicable for?

Enterprises

High Traffic Web Sites


Startups
Enterprise
High Traffic Sites
Reduce network traffic
Reduce response times
Reduce load
Facebook: Bumpersticker
1.4 Million Average Users
1.4 Million Average Users

Average 20 page views


“Push everything you possibly can
to the client to reduce the
amount of traffic going over the
network...”
Startups
HTTP 1.1 Enity Tags
www.nextdaypets.com
\puppies\43
HTTP/1.x 200 OK
Etag: "8b2242293d5e5b02e99b3be73fc0c9fa"
www.nextdaypets.com
\puppies\43
If-None-Match: "8b2242293d5e5b02e99b3be73fc0c9fa"
HTTP/1.x 304 Not Modified
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"

If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT


If-None-Match: "10c24bc-4ab-457e1c1f"
+ Conditional Get
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])

respond_to do |wants|
#...
end
end

end
#response.rb

def last_modified=(utc_time)
def etag=(etag)
#request.rb

def fresh?(response)
def not_modified?(modified_at)
def etag_matches?(etag)
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])

respond_to do |wants|
#...
end
end

end
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])
response.last_modified = @person.updated_at.utc

respond_to do |wants|
#...
end
end

end
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])
response.last_modified = @person.updated_at.utc
response.etag = @person

respond_to do |wants|
#...
end
end

end
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])
response.last_modified = @person.updated_at.utc
response.etag = @person
return head(:not_modified) if request.fresh?(response)

respond_to do |wants|
#...
end
end

end
response.etag = @person # => “5cb44721b6ce18857ff6900486dc4aba”

@person.cache_key # => "people/5-20071224150000"


def fresh_when(options)
def stale?(options)
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])

response.last_modified = @person.updated_at.utc
response.etag = @person
return head(:not_modified) if request.fresh?(response)

respond_to do |wants|
#...
end
end

end
class PeopleController < ApplicationController

def show
@person = Person.find(params[:id])

if stale?(:etag => @person, :last_modified => @person.updated_at.utc)


respond_to do |wants|
#...
end
end
end

end
Last-Modified vs ETag
Later

response.etag = [@admin, @person, flash]


def handle_conditional_get!
if nonempty_ok_response?
self.etag ||= body
if request && request.etag_matches?(etag)
self.status = '304 Not Modified'
self.body = ''
end
end

set_conditional_cache_control! if etag? || last_modified?


end
SInce Feb 2007

self.etag ||= body


http://localhost:3000/people
GET /people HTTP/1.1
http://localhost:3000/people
GET /people HTTP/1.1

HTTP/1.x 200 OK
...
Etag: "94785662c6f60cb96681ed1b09a44783"
http://localhost:3000/people
GET /people HTTP/1.1

HTTP/1.x 200 OK
...
Etag: "94785662c6f60cb96681ed1b09a44783"

http://localhost:3000/people
GET /people HTTP/1.1
If-None-Match:
"94785662c6f60cb96681ed1b09a44783"
http://localhost:3000/people
GET /people HTTP/1.1

HTTP/1.x 200 OK
...
Etag: "94785662c6f60cb96681ed1b09a44783"

http://localhost:3000/people
GET /people HTTP/1.1
If-None-Match:
"94785662c6f60cb96681ed1b09a44783"

HTTP/1.x 304 Not Modified


Etag: "94785662c6f60cb96681ed1b09a44783"
send_file
Assets
Assets
Assets
INODE
/intl/en_ALL/images/logo.gif

ETag: "48b6a5bf-47f4-a0757"
/intl/en_ALL/images/logo.gif /intl/en_ALL/images/logo.gif

ETag: "48b6a5bf-47f4-a0757" ETag: "48b6a5bf-61a-21c86a4"


/intl/en_ALL/images/logo.gif /intl/en_ALL/images/logo.gif

ETag: "48b6a5bf-47f4-a0757" ETag: "48b6a5bf-61a-21c86a4"


Avoid Cache Expiration &
Validation
/stylesheets/screen.css?1219926880

CACHE BUSTER!
Now

<FilesMatch "\.(pdf|flv|jpg|jpeg|png|gif|js|css|swf)$">
Header set Cache-Control "public"
ExpiresActive On
ExpiresDefault “access plus 10 years”
FileETag None
Header unset Last-Modified
Header unset ETag
</FilesMatch>
Server 1 Server 2

/images/beach.png?1241477547 /images/beach.png?1241477554
Option 1
#config/environments/production.rb

#Subversion
ENV['RAILS_ASSET_ID'] = YAML::load(`svn info $RAILS_ROOT`)["Revision"].to_i

#GIT (Check out Grit too)


ENV['RAILS_ASSET_ID'] = File.read(RAILS_ROOT + '/.git/refs/heads/deploy').chomp
Option 2
task :finalize_update, :except => { :no_release => true } do
stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
asset_paths = %w(images stylesheets javascripts).map do |asset|
"#{latest_release}/public/#{p}"
end.join(" ")
run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
end
Option 2

#Capistrano 2.4
set :normalize_asset_timestamps, true
Option 3
Option 3

use-commit-times
Now

ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = true
Proxies &
Via Header
Bag of
Tricks

/stylesheets/screen.css?1219926880

/stylesheets/screen.1219926880.css
#asset_tag_helper.rb
def rewrite_asset_path(source)
#...
source + "?#{asset_id}"
end
#Rules for Versioned Static Files
RewriteRule ^(scripts|css|images)/(.+)\.(.+)\.(js|css|jpg|gif|png)$ $1/$2.$4 [L]
AJAX
DUMB-ASSES
:method => :post
:method => :post
Superfluous values in URL
Superfluous values in URL
HTTP Headers
HTTP Headers
headers['Last-Modified'] = Time.now.httpdate
headers['Expires'] = '-1'
headers['Pragma'] = 'no-cache'
headers['Cache-Control'] = 'no-cache, must-revalidate, max-age=0, pre-check=0, post-check=0'
Now

#http://github.com/dancroak/no_cache
no_cache :first_name_autocomplete, :index
Cache Ajax?
Speed up
rendering
default 2 connections per
host for HTTP 1.1
connections
ActionController::Base.asset_host = "http://mt%d.google.com"
ActionController::Base.asset_host = "http://mt%d.google.com"

http://mt0.google.com

http://mt1.google.com

http://mt2.google.com

http://mt3.google.com
CNAME Now

http://mt0.google.com http://mt1.google.com http://mt2.google.com http://mt3.google.com


greater than 40% drop in page load time
host_names >= 2 && host_names <= 4
Now

# http://github.com/dhh/asset-hosting-with-minimum-ssl

config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
# will serve non-SSL assetts on http://assets[1-4].example.com
"http://assets%d.example.com",
# will serve SSL assets on https://assets1.example.com
"https://assets1.example.com"
)
Less requests are better
Combine Assets
ActionController::Base.perform_caching = true
<%= javascript_include_tag 'application', 'user' %>

<script src="/javascripts/application.js?1219633350" type="text/javascript"></script>


<script src="/javascripts/user.js?1219633368" type="text/javascript"></script>
<%= javascript_include_tag 'application', 'user', :cache => :true %>

<script src="/javascripts/all.js?1219633651" type="text/javascript"></script>


<%= javascript_include_tag 'application', 'user', :cache=>‘login’ %>

<script src="/javascripts/login.js?1219633651" type="text/javascript"></script>


application.js user.js

+
login.js
Compiles on Server

Dedicated Asset Server / CDN

Not all servers may have login.js


Timestamp Conflict

<script src="/javascripts/all.js?1219633651" type="text/javascript"></script>


<script src="/javascripts/all.js?1219634734" type="text/javascript"></script>
asset_packager || cramjam || CSSJSC
Minify
Now

JSMIN || PackR
CSMIN
Smurf
rucksack
minified_cache

YUI Compressor

http://compressorrater.thruhere.net/
bundle_fu
<% bundle do %>

<%= javascript_include_tag "prototype" %>


<%= stylesheet_link_tag "basic.css" %>
<%= calendar_date_select_includes "red" %>
<script src="javascripts/application.js" type="text/javascript"></script>

<% end %>

<script src="/javascripts/cache/bundle.js?1220211999" type="text/javascript"></script>


Generates files locally
during development
www.example.org dynamic content
static.example.org components (cookies don’t go here)
Now

www.example.org dynamic content


static.example.org components (cookies don’t go here)
CDN & Asset Servers
Let Google be your CDN
Google Ajax Libraries
jQuery

prototype

script.aculo.us

MooTools

dojo
“Once we host a release of a given library, we
are committed to hosting that release
indefinitely”
Last-Modified: Fri, 30 May 2008 06:03:19 GMT
Expires: Sun, 17 Jan 2038 19:14:07 GMT
Cache-Control: public
Date: Sat, 30 Aug 2008 20:10:26 GMT
Cache-Control: public
mod_deflate vs mod_gzip
mod_deflate vs mod_gzip
Baked w/ Apache
Easier on CPU
~35% Compresion
mod_deflate vs mod_gzip
Baked w/ Apache ~29% Compresion
Easier on CPU
~35% Compresion
config.middleware.use Rack::Deflater
GZip + Minification +
Minimum Components
Fewer Requests

Smaller Responses
There is more potential for improvement by focusing on the front-end.

Cutting it in half reduces response times by 40% or more, whereas cutting


back-end performance in half results in less than a 10% reduction.
Front-end improvements typically require less time and resources than
back-end projects (redesigning application architecture and code, finding
and optimizing critical code paths, adding or modifying hardware,
distributing databases, etc.).
yslow
LiveHTTPHeaders
Fiddler
http://compressorrater.thruhere.net/
Matthew Deiters
www.theAgileDeveloper.com

Das könnte Ihnen auch gefallen