Professional Documents
Culture Documents
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
Enterprises
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”
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])
end
Last-Modified vs ETag
Later
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"
ETag: "48b6a5bf-47f4-a0757"
/intl/en_ALL/images/logo.gif /intl/en_ALL/images/logo.gif
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
#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://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' %>
+
login.js
Compiles on Server
JSMIN || PackR
CSMIN
Smurf
rucksack
minified_cache
YUI Compressor
http://compressorrater.thruhere.net/
bundle_fu
<% bundle do %>
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.