Upload
michael-may
View
510
Download
3
Embed Size (px)
DESCRIPTION
When it comes to caching, there are two types of web developers - those with phat stacks of cache money and those suffering from cache anxiety. Caching is particularly handy when scaling Rails apps, however we often avoid putting in effort because it can quickly get complicated without effective strategies. Rails provides a host of built-in caching interfaces that are easy to leverage and extend. I’ll talk about how to do this and combine rails with technologies like CDNs and HTTP accelerators like Varnish so that you can more effectively cache everything, everywhere without fear of serving stale content. Michael May is an API Engineer at Fastly and a former Austinite, now hailing from San Francisco. While in Texas he studied at UT Austin and co-founded CDN Sumo, which was acquired by Fastly. He’s waiting for the day when FaaS (Franklin BBQ as a Service) becomes a thing and dreams about fast websites.
Citation preview
Rails Caching Secrets: From the Edge!
Michael May | @ohaimmay | austinonrails | 10/28/2014
We’re Hiring!Distributed Systems! Ruby C Go Perl JS
We are here
• Rails caching best practices
• Dynamic content
• Edge caching / Content Delivery Networks
• Edge caching dynamic content with Rails
Rails caching
• Query/SQL
• Page/Action (removed from Rails 4 core!)
• Asset
• Fragment
config.action_controller.perform_caching = true
Rails caching
Query caching
• Automagically done by rails when perform_caching = true
• Not cached between requests!
• Could just store the query result in a variable
class Product < MyActiveModel
def self.latest Rails.cache.fetch("latest", expires_in: 8.hours) do Product.last(50) end end
end
Custom Query Caching
Asset Pipeline• Serve static assets from nginx or apache
• config.serve_static_assets = false
• Enable Compression*
• config.assets.compress = true
• # Rails 4config.assets.css_compressor = :yuiconfig.assets.js_compressor = :uglifier
• Asset Digests
• config.assets.digest = true
Enable Compression*
* http://robots.thoughtbot.com/content-compression-with-rack-deflater
# in application.rb module FastestAppEver class Application < Rails::Application config.middleware.use Rack::Deflater end end
Compress all responses with gzip, deflate, etc.
$ curl -sv -H “Accept-Encoding: deflate” \http://fast.mmay.rocks/catz.json
* Connected to fast.mmay.rocks (127.0.0.1) port 666 > GET /catz.json HTTP/1.1 > User-Agent: curl > Host: fast.mmay.rocks:666 > Accept-Encoding: deflate,gzip> < HTTP/1.1 200 OK < Content-Type: application/json; charset=utf-8 < Vary: Accept-Encoding< Content-Encoding: deflate< Cache-Control: max-age=60, s-maxage=3600 < Transfer-Encoding: chunked < * magical encoded bytes* V*.I,)-VRJ-*RN@ ɥEEy%9
Before
After
Asset Caching
• Configure an asset host if needed
• config.action_controller.asset_host = ENV[‘FASTLY_CDN_URL']
• Cache-Control like a pro
• config.static_cache_control = 'public, s-maxage=15552000, maxage=2592000'
HTTP HeadersRFC 2616 Section 14
Cache-Control HTTP Header“public, maxage=2592000, s-maxage=15552000”
public“please cache me”
maxage=2592000“keep me for 30 days”
s-maxage=15552000“PROXIES ONLY! - Keep me for 180 days”
Bonus Cache-Control Directives!
• stale-while-revalidate
• Serve the cached (stale) content for n seconds while you re-fetche the new content in the background
• Cache-Control: maxage=604800, stale-while-revalidate=3600
• “Serve stale for up to an hr while you fetch the latest behind the scenes”
• stale-if-error
• If the re-fetch fails within n seconds of the content becoming stale, serve the cached content
• Cache-Control: max-age=604800, stale-if-error=86400
• “Serve stale for up to an hr if origin responds with 4xx or 5xx”
ETags• Automatically added into requests with
Rack::ETag
• Rails renders response every time to calculate etag
• Override default with Conditional GETs
• stale?(@model)
• fresh_when(@model)
The Vary HTTP Header• Change response base on the value of another
HTTP Header
• Example:“Vary: Accept-Encoding”Accept-Encoding: gzip => Serve Response A Accept-Encoding: deflate => Serve Response B
• “This response changes for different values of the Accept-Encoding header”
Vary Best Practices
• Please do not Vary on User-Agent
• There are THOUSANDS of these!
• Limits caching benefits - almost Impossible to serve the same response more than once!
• In general, avoid varying on anything other than content encoding
Dynamic Content• Changes are unpredictable!
• User driven events
• Can’t just set a Time To Live (TTL)
• Frequently, but not continuously changing
• Actually static for short periods of time (we can cache static things)!
Dynamic Content Caching
• Usually don’t (╯°□°)╯︵ ┻━┻
• Edge Side Includes (ESI)
• Dynamic Site Acceleration (DSA)
Fragment CachingThe rails answer to caching dynamic HTML
# products/index.html.erb <% cache(cache_key_for_products) do %> <% Product.all.each do |p| %> <%= link_to p.name, product_url(p) %> <% end %> <% end %>
# products_controller.rb def update … expire_fragment(cache_key_for_products) … end
Nested Fragment Caching
<% cache(cache_key_for_products) do %> All available products: <% Product.all.each do |p| %>
<% cache(p) do %> <%= link_to p.name, product_url(p) %> <% end %>
<% end %> <% end %>
Nested Fragment• Tedious
• Comb through (probably terrible) view code
• Cache keys are weird
• “A given key should always return the same content.” - Rails
• But I like “A given key should always return the most up-to-date content” - like a DB primary key
• Hacking around cache limitations
• Memcache and wildcard purging
Nested Fragment• Garbage left in the cache
• Defaults writing to disk
• What about dynamic API caching?
• “The caching itself happens in the views based on partials rendering the objects in question”
• Take control over your cached data!
Edge Cachingwith things like CDNs
Edge Caches
• Geographically distributed
• Highly optimized storage and network (nanoseconds count)
• Move content physically closer to end-users
• DECREASE LATENCY!(speed of light sux lol)
#cachemoney
• Less requests/bandwidth back to your origin server
• Avoid complex or less efficient strategies
• Edge Side Includes (ESI)
• Fragment view caching
Edge caching dynamic content
Our approach to dynamic content
• Tag content with Surrogate-Key HTTP headers
• Programmatically purge (~150ms globally)
• By Surrogate-Key
• By resource path
• Real-time analytics and log streaming
• Optimize the pieces of the network we control
Tagging responses with Surrogate-Keys
class ProductsController < ApplicationController # set Cache-Control, strip Set-Cookie before_filter :set_cache_control_headers,only [:index,:show] def index @products = Product.last(10) # set Surrogate-Key: products set_surrogate_key_header @products.table_key respond_with @products end def show @product = Products.find(params[:id]) # set Surrogate-Key: product/666 set_surrogate_key_header @product.record_key respond_with @product end end
Purge on updates
class ProductsController < ApplicationController def create @product = Product.new(params) if @product.save # purge Surrogate-Key: products @product.purge_all render @product end end ...
def update @product = Product.find(params[:id]) if @product.update(params) # purge Surrogate-Key: product/666 @product.purge render @product end end
fastly-railsgithub.com/fastly/fastly-rails
Edge caching in practice
Watch out for Set-Cookie!
• Nothing with a Set-Cookie header is cached (by default)
• Authentication frameworks/middleware might inject Set-Cookie after the rails stack removes it
• Avoid caching pains by knowing when, where, and how you use Set-Cookie
Edge scripting with VCL(varnish config lang)
VCL
• Fastly VCL Extensions
• date/time, geoip, hashing, strings, etc.
• Do application logic at the CDN edge
URL Rewriting
• Filter bad requests
• Normalize or block paths
• Apache, nginx
• if ($invalid_referer) { return 403; }
• You can do this at the edge!
Synthetic Responses
What can we do better?• Add better caching defaults?
• Cache-Control, stale-while-revalidate, stale-if-error
• Re-use existing rails cache interfaces for edge caching?
• ActiveSupport::Cache::EdgeStore
• Better integration with HTTP accelerators like Varnish?
Takeaways• Take advantage of Rails built-in caching
• Get fancy with Cache-Control directives
• Use Google PageSpeed Insights (chrome plugin adds it to dev tools)
• Dynamic edge caching is all about the power of purge!
• Similar school of thought to rails action caching
Questions?
Michael May || @ohaimmay
cool links: fastly-rails - github.com/fastly/fastly-rails surrogate keys - fastly.com/blog/surrogate-keys-part-1 cache-control tutorial - docs.fastly.com/guides/tutorials/cache-control-tutorial serve stale cache-control - fastly.com/blog/stale-while-revalidate vary header best practices - fastly.com/blog/best-practices-for-using-the-vary-header caching like & share buttons - fastly.com/blog/caching-like-and-share-buttons pagespeed insights - developers.google.com/speed/pagespeed