Transcript
Page 1: Accelerating Rails with edge caching

Accelerating Rails with edge cachingMichael May | @ohaimmay | SFRails | 10/16/2014

Page 2: Accelerating Rails with edge caching
Page 3: Accelerating Rails with edge caching

Topic

• Rails caching best practices

• Dynamic content/caching

• Edge caching

• Edge caching dynamic content with Rails

Page 4: Accelerating Rails with edge caching

Rails caching

• Query/SQL

• Page/Action (removed from Rails 4 core)

• Asset

• Fragment

Page 5: Accelerating Rails with edge caching

config.action_controller.perform_caching = true

Rails caching

Page 6: Accelerating Rails with edge caching

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: Accelerating Rails with edge caching

class Product < MyModel

def self.out_of_stock Rails.cache.fetch("out_of_stock", expires_in: 1.hour) do Product.where("inventory.quantity = 0") end end

end

Manual Query Caching

Page 8: Accelerating Rails with edge caching

Asset Caching• Serve static assets from a proxy

• config.serve_static_assets = false

• Enable Compression*

• config.assets.compress = true

• # In Rails 4

• config.assets.css_compressor = :yui

• config.assets.js_compressor = :uglifier

• Asset Digests

• config.assets.digest = true

Page 9: Accelerating Rails with edge caching

https://fast.mmay.rocks/assets/catzlol-75408509152249b79b818b252da51bc4.png

Page 10: Accelerating Rails with edge caching

Enable Compression*

* http://robots.thoughtbot.com/content-compression-with-rack-deflater

module FastestAppEver

class Application < Rails::Application config.middleware.use Rack::Deflater end

end

Compress HTML, JSON responses at runtime

Page 11: Accelerating Rails with edge caching
Page 12: Accelerating Rails with edge caching
Page 13: Accelerating Rails with edge caching

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: Accelerating Rails with edge caching

Cache-Controlpublic, s-maxage=15552000, maxage=2592000

public“please cache me”

maxage=2592000“keep me for 30 days”

s-maxage=15552000“PROXIES ONLY! - Keep me for 180 days”

Page 15: Accelerating Rails with edge caching

Keepin’ it fresh• stale-while-revalidate

• Serve the current (stale) version for n seconds while it re-fetches the latest version in the background

• Cache-Control: max-age=604800, stale-while-revalidate=86400

• stale-if-error

• If the re-fetch fails within n seconds of the response becoming stale, serve the cached response

• Cache-Control: max-age=604800, stale-while-revalidate=86400, stale-if-error=259200

Page 16: Accelerating Rails with edge caching

The Vary HTTP Header

• In general, never Vary on anything other than Content-Encoding

• Varying makes it impossible to serve the same response more than once and limits caching benefits

• NEVER Vary on User-Agent!

• There are THOUSANDS of these!

Page 17: Accelerating Rails with edge caching
Page 18: Accelerating Rails with edge caching

Dynamic Content

Page 19: Accelerating Rails with edge caching

Dynamic Content

• Changes are unpredictable!

• user driven events

• Can’t just set a Time To Live (TTL)

Page 20: Accelerating Rails with edge caching

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: Accelerating Rails with edge caching

Dynamic Content Caching

• Usually don’t

• Edge Side Includes (ESI)

• Dynamic Site Acceleration (DSA)

Page 22: Accelerating Rails with edge caching

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.rbdef update … expire_fragment(cache_key_for_products) …end

Page 23: Accelerating Rails with edge caching

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: Accelerating Rails with edge caching

Nested Fragment Issues

• Tedious

• Comb through (probably terrible) view code

• Cache keys are weird

• “A given key should always return the same content.” - DHH

• products/15-20110218104500

• “A given key should always return the most up-to-date content.” - Me

• products/15

• Hacks around cache limitations

• Memcache has no wildcard purging!

Page 25: Accelerating Rails with edge caching

Nested Fragment Issues

• Garbage left in the cache

• Defaults writing to disk

• Memcached, Redis, etc

• Probably lives in the same DC as your app server

• Distributing, replication takes effort

• 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: Accelerating Rails with edge caching

Edge Caching

Page 27: Accelerating Rails with edge caching

Edge Cachingaka content delivery network

aka CDN

Page 28: Accelerating Rails with edge caching

Edge Cache

• Geographically distributed

• Highly optimized storage and network (nanoseconds count)

• Move content physically closer to the end-users

• End goal - DECREASE LATENCY!

Page 29: Accelerating Rails with edge caching
Page 30: Accelerating Rails with edge caching
Page 31: Accelerating Rails with edge caching
Page 32: Accelerating Rails with edge caching
Page 33: Accelerating Rails with edge caching
Page 34: Accelerating Rails with edge caching

The more content we can offload, the better performance

we get

Page 35: Accelerating Rails with edge caching

#cachemoney• App servers cost real cash money (not

cache money)

• Less requests back to your application server

• Avoid complex or less efficient strategies

• Edge Side Includes (ESI)

• Fragment caching

Page 36: Accelerating Rails with edge caching

Edge caching the dynamic content

Page 37: Accelerating Rails with edge caching
Page 38: Accelerating Rails with edge caching

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 hell out of the pieces of the network we can control

Page 39: Accelerating Rails with edge caching

Tagging responses with Surrogate-Key

Page 40: Accelerating Rails with edge caching

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 endend

Page 41: Accelerating Rails with edge caching

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 endend

Page 42: Accelerating Rails with edge caching

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 endend

Page 43: Accelerating Rails with edge caching

Purge on updates

Page 44: Accelerating Rails with edge caching

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

Page 45: Accelerating Rails with edge caching

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

Page 46: Accelerating Rails with edge caching

fastly-railsgithub.com/fastly/fastly-

rails

Page 47: Accelerating Rails with edge caching

Edge caching in practice

Page 48: Accelerating Rails with edge caching

Be aware of cookies

• 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 49: Accelerating Rails with edge caching

edge scripting

Page 50: Accelerating Rails with edge caching

URL Rewriting

• Apache, nginx, etc support URL Rewriting

• Filter bad requests

• Normalize paths

Page 51: Accelerating Rails with edge caching

URL Rewrite at the edge

• Varnish HTTP cache

• VCL

• Requests never hit origin!

Page 52: Accelerating Rails with edge caching

#winning

meh

yay

Page 53: Accelerating Rails with edge caching

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

• More fine-grained integration with HTTP accelerators like Varnish?

Page 54: Accelerating Rails with edge caching

Takeaways• Rails has tons of built-in caching options

• 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 55: Accelerating Rails with edge caching

Questions?

Contact: Michael May | @ohaimmay | [email protected]

cool links:fastly-rails - github.com/fastly/fastly-railssurrogate keys - fastly.com/blog/surrogate-keys-part-1cache-control tutorial - docs.fastly.com/guides/tutorials/cache-control-tutorialserve stale cache-control - fastly.com/blog/stale-while-revalidatevary header best practices - fastly.com/blog/best-practices-for-using-the-vary-headercaching like & share buttons - fastly.com/blog/caching-like-and-share-buttonspagespeed insights - developers.google.com/speed/pagespeed


Top Related