Rails Caching Secrets from the Edge

Post on 02-Jul-2015

510 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

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.

Transcript

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

top related