$30 off During Our Annual Pro Sale. View Details »

Advanced Caching in Rails

Adam Hawkins
September 21, 2012

Advanced Caching in Rails

Presented at Frozen Rails in Helsinki 2012

Adam Hawkins

September 21, 2012
Tweet

More Decks by Adam Hawkins

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. You May Have Read This

    View Slide

  4. Zero to Sixty

    View Slide

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

    View Slide

  6. 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!

    View Slide

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

    View Slide

  8. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. 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

    View Slide

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

    View Slide

  14. 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

    View Slide

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

    View Slide

  16. # Action Caching
    class PostsController < ApplicationController
    caches_action :index
    end

    View Slide

  17. # Fragment Caching
    <% cache 'posts' do %>

    <% end %>

    View Slide

  18. # 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"

    View Slide

  19. 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

    View Slide

  20. Hyvä, we can cache
    things

    View Slide

  21. Perkele! Now we to
    expire things

    View Slide

  22. View Slide

  23. View Slide

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

    View Slide

  25. 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

    View Slide

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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

  29. 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.

    View Slide

  30. /posts/1/12348971239487
    updated_at or last modified date

    View Slide

  31. Damn, that was easy

    View Slide

  32. View Slide

  33. Use Cases

    View Slide

  34. # 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

    View Slide

  35. • 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

    View Slide

  36. <%# _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.

    View Slide

  37. 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

    View Slide

  38. # 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

    View Slide

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

    View Slide

  40. 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”.

    View Slide

  41. 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.

    View Slide

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

    View Slide