Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. Tervetuloa! • Olen Adam Hawkins • @adman65 • gh://twinturbo •

    broadcastingadam.com • https://speakerdeck.com/u/twinturbo • Lead code money at Radium
  2. 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!
  3. 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
  4. HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Adds

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

    an ETag header if applicable • ETag is hash of the body
  6. 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
  7. 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
  8. # HTTP Caching def show # 304 if fresh #

    else respond_with if stale? @post respond_with @post end end
  9. 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
  10. “There are only two hard problems in Computer Science: GNU/Linux

    and cache invalidation” - RMS paraphrasing Phil Karlton
  11. 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
  12. Approaches • Dependency graphs • Auto-expiring cache keys • Manual

    expiration (puke) • Break cacheable content into smaller discrete chunks
  13. 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
  14. 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
  15. 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.
  16. # 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
  17. • 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
  18. <%# _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.
  19. 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
  20. # 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
  21. Date Handling • Write the UTC date into a data-timestamp

    attribute. • Use javascript to parse timestamps into local dates on the frontend
  22. 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”.
  23. 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.