49
Rails Caching Secrets: From the Edge! Michael May | @ohaimmay | austinonrails | 10/28/2014

Rails Caching Secrets from the Edge

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

Page 1: Rails Caching Secrets from the Edge

Rails Caching Secrets: From the Edge!

Michael May | @ohaimmay | austinonrails | 10/28/2014

Page 2: Rails Caching Secrets from the Edge

We’re Hiring!Distributed Systems! Ruby C Go Perl JS

Page 3: Rails Caching Secrets from the Edge

We are here

• Rails caching best practices

• Dynamic content

• Edge caching / Content Delivery Networks

• Edge caching dynamic content with Rails

Page 4: Rails Caching Secrets from the Edge

Rails caching

• Query/SQL

• Page/Action (removed from Rails 4 core!)

• Asset

• Fragment

Page 5: Rails Caching Secrets from the Edge

config.action_controller.perform_caching = true

Rails caching

Page 6: Rails Caching Secrets from the Edge

Query caching

• Automagically done by rails when perform_caching = true

• Not cached between requests!

• Could just store the query result in a variable

Page 7: Rails Caching Secrets from the Edge

class Product < MyActiveModel

def self.latest Rails.cache.fetch("latest", expires_in: 8.hours) do Product.last(50) end end

end

Custom Query Caching

Page 8: Rails Caching Secrets from the Edge

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

Page 9: Rails Caching Secrets from the Edge

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.

Page 10: Rails Caching Secrets from the Edge

$ 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

Page 11: Rails Caching Secrets from the Edge
Page 12: Rails Caching Secrets from the Edge

Before

After

Page 13: Rails Caching Secrets from the Edge

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'

Page 14: Rails Caching Secrets from the Edge

HTTP HeadersRFC 2616 Section 14

Page 15: Rails Caching Secrets from the Edge

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”

Page 16: Rails Caching Secrets from the Edge

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”

Page 17: Rails Caching Secrets from the Edge

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)

Page 18: Rails Caching Secrets from the Edge

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”

Page 19: Rails Caching Secrets from the Edge

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

Page 20: Rails Caching Secrets from the Edge

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)!

Page 21: Rails Caching Secrets from the Edge

Dynamic Content Caching

• Usually don’t (╯°□°)╯︵ ┻━┻

• Edge Side Includes (ESI)

• Dynamic Site Acceleration (DSA)

Page 22: Rails Caching Secrets from the Edge

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

Page 23: Rails Caching Secrets from the Edge

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 %>

Page 24: Rails Caching Secrets from the Edge

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

Page 25: Rails Caching Secrets from the Edge

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!

Page 26: Rails Caching Secrets from the Edge

Edge Cachingwith things like CDNs

Page 27: Rails Caching Secrets from the Edge

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)

Page 28: Rails Caching Secrets from the Edge
Page 29: Rails Caching Secrets from the Edge
Page 30: Rails Caching Secrets from the Edge
Page 31: Rails Caching Secrets from the Edge
Page 32: Rails Caching Secrets from the Edge

#cachemoney

• Less requests/bandwidth back to your origin server

• Avoid complex or less efficient strategies

• Edge Side Includes (ESI)

• Fragment view caching

Page 33: Rails Caching Secrets from the Edge

Edge caching dynamic content

Page 34: Rails Caching Secrets from the Edge

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

Page 35: Rails Caching Secrets from the Edge

Tagging responses with Surrogate-Keys

Page 36: Rails Caching Secrets from the Edge

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

Page 37: Rails Caching Secrets from the Edge

Purge on updates

Page 38: Rails Caching Secrets from the Edge

class ProductsController < ApplicationController def create @product = Product.new(params) if @product.save # purge Surrogate-Key: products @product.purge_all render @product end end ...

Page 39: Rails Caching Secrets from the Edge

def update @product = Product.find(params[:id]) if @product.update(params) # purge Surrogate-Key: product/666 @product.purge render @product end end

Page 40: Rails Caching Secrets from the Edge

fastly-railsgithub.com/fastly/fastly-rails

Page 41: Rails Caching Secrets from the Edge

Edge caching in practice

Page 42: Rails Caching Secrets from the Edge

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

Page 43: Rails Caching Secrets from the Edge

Edge scripting with VCL(varnish config lang)

Page 44: Rails Caching Secrets from the Edge

VCL

• Fastly VCL Extensions

• date/time, geoip, hashing, strings, etc.

• Do application logic at the CDN edge

Page 45: Rails Caching Secrets from the Edge

URL Rewriting

• Filter bad requests

• Normalize or block paths

• Apache, nginx

• if ($invalid_referer) { return 403; }

• You can do this at the edge!

Page 46: Rails Caching Secrets from the Edge

Synthetic Responses

Page 47: Rails Caching Secrets from the Edge

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?

Page 48: Rails Caching Secrets from the Edge

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

Page 49: Rails Caching Secrets from the Edge

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