Advanced Caching in Rails

94378c403019af23a28b08447a34b8e0?s=47 Adam Hawkins
September 21, 2012

Advanced Caching in Rails

Presented at Frozen Rails in Helsinki 2012

94378c403019af23a28b08447a34b8e0?s=128

Adam Hawkins

September 21, 2012
Tweet

Transcript

  1. 2.

    Tervetuloa! • Olen Adam Hawkins • @adman65 • gh://twinturbo •

    broadcastingadam.com • https://speakerdeck.com/u/twinturbo • Lead code money at Radium
  2. 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!
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 15.

    # HTTP Caching def show # 304 if fresh #

    else respond_with if stale? @post respond_with @post end end
  9. 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
  10. 22.
  11. 23.
  12. 24.

    “There are only two hard problems in Computer Science: GNU/Linux

    and cache invalidation” - RMS paraphrasing Phil Karlton
  13. 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
  14. 26.

    Approaches • Dependency graphs • Auto-expiring cache keys • Manual

    expiration (puke) • Break cacheable content into smaller discrete chunks
  15. 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
  16. 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
  17. 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.
  18. 32.
  19. 33.
  20. 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
  21. 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
  22. 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.
  23. 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
  24. 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
  25. 39.

    Date Handling • Write the UTC date into a data-timestamp

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