When to tell your kids about presentation caching
Matthew Deiters www.theAgileDeveloper.com
When to tell your kids about presentation caching Matthew Deiters - - PowerPoint PPT Presentation
When to tell your kids about presentation caching Matthew Deiters www.theAgileDeveloper.com A practical guide to stuffing your apps bits into someone elses browser Questions: @mdeiters Rapid Feature Development Rapid Feature
Matthew Deiters www.theAgileDeveloper.com
Rapid Feature Development
Rapid Feature Development Adoption & Growth
client.is_a?(Browser) == true
Last-Modified Header
max-age Header
Expires Header
Reducing Network Trac
GZip Minification
Cookies
www.nextdaypets.com
HTTP/1.x 200 OK Etag: "8b2242293d5e5b02e99b3be73fc0c9fa"
www.nextdaypets.com
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"
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]) respond_to do |wants| #... end end end response.last_modified = @person.updated_at.utc
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) respond_to do |wants| #... end end end response.last_modified = @person.updated_at.utc response.etag = @person
class PeopleController < ApplicationController def show @person = Person.find(params[:id]) respond_to do |wants| #... end end end return head(:not_modified) if request.fresh?(response) response.last_modified = @person.updated_at.utc response.etag = @person
response.etag = @person # => “5cb44721b6ce18857ff6900486dc4aba” @person.cache_key # => "people/5-20071224150000"
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
response.etag = [@admin, @person, flash]
Later
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
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"
/intl/en_ALL/images/logo.gif ETag: "48b6a5bf-47f4-a0757"
/intl/en_ALL/images/logo.gif ETag: "48b6a5bf-47f4-a0757" /intl/en_ALL/images/logo.gif ETag: "48b6a5bf-61a-21c86a4"
/intl/en_ALL/images/logo.gif ETag: "48b6a5bf-47f4-a0757" /intl/en_ALL/images/logo.gif ETag: "48b6a5bf-61a-21c86a4"
/stylesheets/screen.css?1219926880
CACHE BUSTER!
<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>
Now
/images/beach.png?1241477547 /images/beach.png?1241477554 Server 1 Server 2
#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
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
#Capistrano 2.4 set :normalize_asset_timestamps, true
Now
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = true
/stylesheets/screen.css?1219926880 /stylesheets/screen.1219926880.css
Bag of Tricks
#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]
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'
#http://github.com/dancroak/no_cache no_cache :first_name_autocomplete, :index
Now
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
http://mt0.google.com http://mt1.google.com http://mt2.google.com http://mt3.google.com
Now
host_names >= 2 && host_names <= 4
# 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" )
Now
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
Dedicated Asset Server / CDN Not all servers may have login.js
<script src="/javascripts/all.js?1219633651" type="text/javascript"></script> <script src="/javascripts/all.js?1219634734" type="text/javascript"></script>
rucksack
minified_cache
YUI Compressor
http://compressorrater.thruhere.net/ Now
<% 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>
www.example.org dynamic content static.example.org components (cookies don’t go here)
www.example.org dynamic content static.example.org components (cookies don’t go here) Now
“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
Baked w/ Apache Easier on CPU ~35% Compresion
Baked w/ Apache Easier on CPU ~35% Compresion ~29% Compresion
config.middleware.use Rack::Deflater
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.).
Matthew Deiters www.theAgileDeveloper.com