Slide 1

Slide 1 text

Advanced Caching in Rails Presented with love in Helsinki for Frozen Rails 2012

Slide 2

Slide 2 text

Tervetuloa! • Olen Adam Hawkins • @adman65 • gh://twinturbo • broadcastingadam.com • https://speakerdeck.com/u/twinturbo • Lead code money at Radium

Slide 3

Slide 3 text

You May Have Read This

Slide 4

Slide 4 text

Zero to Sixty

Slide 5

Slide 5 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag

Slide 6

Slide 6 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag 200 or 304 • Cache-Control • ETag • If-None-Match • If-Modified-Since • Protocol level caching -- take advantage!

Slide 7

Slide 7 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag Middleware to support HTTP Caching

Slide 8

Slide 8 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Follows HTTP caching rules • Respect freshness • Stores cacheable content in it’s own store • Forwards request down the stack

Slide 9

Slide 9 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Adds a 304 Not Modified header • Continues the request based on freshness

Slide 10

Slide 10 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Adds an ETag header if applicable • ETag is hash of the body

Slide 11

Slide 11 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag LOL, No

Slide 12

Slide 12 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Captures body and headers • Dump it in Rails.cache • Like HTTP caching, but worse

Slide 13

Slide 13 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Traditionally used to cache HTML • Only usable in views

Slide 14

Slide 14 text

HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Writes content to the store • Usually Memcache • Actions and Fragments are built on this • Can cache whatever you like • Use it

Slide 15

Slide 15 text

# HTTP Caching def show # 304 if fresh # else respond_with if stale? @post respond_with @post end end

Slide 16

Slide 16 text

# Action Caching class PostsController < ApplicationController caches_action :index end

Slide 17

Slide 17 text

# Fragment Caching <% cache 'posts' do %> <% end %>

Slide 18

Slide 18 text

# Rails.cache Rails.cache.write("hash", {:this => :hash}) Rails.cache.write "foo", "bar" Rails.cache.read "foo" Rails.cache.read "hash" Rails.cache.read "foo"

Slide 19

Slide 19 text

What do I use? • Rails.cache when you want to cache arbitrary computations (anywhere in your code!) • HTTP caching everywhere • Fragment caching for HTML • Action and page caching, not so much

Slide 20

Slide 20 text

Hyvä, we can cache things

Slide 21

Slide 21 text

Perkele! Now we to expire things

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

“There are only two hard problems in Computer Science: GNU/Linux and cache invalidation” - RMS paraphrasing Phil Karlton

Slide 25

Slide 25 text

Why So Hard? • Data is usually associated or dependent • Data is usually used to compose views • Output depend on different parts of data • Data can change in many places • Not all changes happen during HTTP requests (worker queues) • This is actually really hard

Slide 26

Slide 26 text

Approaches • Dependency graphs • Auto-expiring cache keys • Manual expiration (puke) • Break cacheable content into smaller discrete chunks

Slide 27

Slide 27 text

Dependency Graphs • The most complex thing that could possibly work • Track associations • Walk the graph to connected nodes when one node changes. Expiring nodes as you go along • gh://jcoglan/primer

Slide 28

Slide 28 text

Manual Expiration • Rails.cache.delete “foo” • Sweepers -- seriously who use these? • Extremely complex because you must know every possible cached thing ever • Waste of time, don’t do it

Slide 29

Slide 29 text

Auto-expiring Keys • The easiest thing that could possibly work • No need to manually expire • Cache key represents object state • Includes a hash or timestamp • PROTIP: reduce data to smallest unit where changes can be tracked.

Slide 30

Slide 30 text

/posts/1/12348971239487 updated_at or last modified date

Slide 31

Slide 31 text

Damn, that was easy

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Use Cases

Slide 34

Slide 34 text

# Caching Collections class ActiveRecord::Relation def cache_key scoped.maximum(:updated_at).to_i end end # In your view for fragments cache @posts # for HTTP caching fresh? @posts

Slide 35

Slide 35 text

• Cache one thing that caches another • Simple example: a cache list of posts. Each post is cached as well. List of posts uses a composite cache key. Expiring one post expires the list. Rerendering the list only needs the updated post. Everything else is still cached. • Magically coming to Rails 4* Russian Doll Caching

Slide 36

Slide 36 text

<%# _posts.erb %> <%# the _posts partial gets linked to the _post partial %> <%# so a change there will expire this partial %> <%= render :partial => "posts" %> <%# _post.erb %> <%= post.title %> <%# you get some crazy cache key %> views/posts/065816632-20120810191209/d946707c67ab41d93cb2 Input hash. Input changes expire cache keys.

Slide 37

Slide 37 text

JSON APIs w/AMS • AMS = ActiveModel::Serializers • HTTP caching everywhere • Auto expiring cache keys • Cache individual JSON fragments, responses composed of discrete JSON fragments. • belongs_to :touch => true • Russian doll caching for collections and records with associations

Slide 38

Slide 38 text

# ActiveModel::Serializers Patch class PostSerializer < ActiveModel::Serializer # cache JSON string def to_json(*args) Rails.cache.fetch "/posts/#{object.cache_key}/to_json" do super end end # cache generated hash def serializable_hash(*args) Rails.cache.fetch "/posts/#{object.cache_key}/hash" do super end end end

Slide 39

Slide 39 text

Date Handling • Write the UTC date into a data-timestamp attribute. • Use javascript to parse timestamps into local dates on the frontend

Slide 40

Slide 40 text

Data Sharing • Eliminate permutations by using one generic copy. • Saves space in memcache • Example: the current user’s name is replaced with “You” on pages. The underlying view contains all the names. Use Javascript to replace the current user’s name with “You”.

Slide 41

Slide 41 text

Wrap-Up • Use HTTP caching everywhere • Use auto expiring keys everywhere (belongs_to :touch => true) • Don’t use sweepers, page or action caching • Beware of things that happen outside the HTTP Request • So much to cover, not enough time.

Slide 42

Slide 42 text

Questions? Read This • http://www.broadcastingadam.com/ 2012/07/advanced_caching_revised/