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
HTTP Page Action Fragment Rails.cache Rack::Cache Rack::ConditionalGet Rack::ETag • Adds a 304 Not Modified header • Continues the request based on freshness
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
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
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
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
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
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
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.
# 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
• 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
<%# _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.
# 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
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”.
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.