<swept-cache>

<swept-cache> is a fragment cache that stores context dependency information for itself and all contained inner swept-cache’s. Dependencies are not checked if the cache is hit. This means that swept-cache should be considerably faster than <nested-cache>, but it does require that you create sweepers for your caches. These sweepers can use the stored dependency information to invalidate the appropriate fragments.

Example

<def tag="card" for="Foo">
  <swept-cache route-on suffix="card">
    <card without-header>
      <body:><view:body/></body>
    </card>
  </nested-cache>
</def>

<def tag="view" for="Bar">
  <swept-cache route-on suffix="view">
    <view:body/>
    <swept-cache:foos route-on="&this_parent" suffix="collection">
      <collection/>
    <swept-cache>
  </swept-cache>
</def>

class FooSweeper < ActionController::Caching::Sweeper
  observe Foo

  def after_create(foo)
    expire_swept_caches_for(foo.bar, :foos)
  end

  def after_update(foo)
    expire_swept_caches_for(foo)
    expire_swept_caches_for(foo.bar, :foos)
  end

  def after_destroy(foo)
    expire_swept_caches_for(foo)
    expire_swept_caches_for(foo.bar, :foos)
  end
end

class BarSweeper < ActionController::Caching::Sweeper
  observe Bar

  def after_update(bar)
    expire_swept_caches_for(bar)
  end

  def after_destroy(bar)
    expire_swept_caches_for(bar)
  end
end

In the above example, if a Foo gets updated, the following fragment caches will be invalidated:

  • the card for the foo
  • the collection of foos inside bar
  • the bar view
  • any pages that have a swept-cache that contains a view of bar

When outer caches are rebuilt, inner caches that are still valid may be used as is.

Specifying the Context

swept-cache assumes that the cache is dependent on the current context (aka this) as well as the context of any contained swept-cache’s.

The context must be either an object that has been saved to the database, or an attribute on an object that has been saved to the database. If it is not one of these two, you must either switch the context to something that is, or specify the dependencies manually.

When specifying the dependencies manually, you pass a list of database objects, database objects plus an attribute name, and/or strings.

<swept-cache dependencies="&[this, [this, :comments], foo, :all_foos]"

Note that when dependencies are specified manually, this must be added to the list, if so desired.

Also note that dependencies are not added to the cache key.

Attributes

All extra attributes are used as non-hierarchical cache keys, so for inner caches these should either be constants or be manually propagated to the outer caches

dependencies: see above. Default is &[this]

query-params: A comma separated list of query (or post) parameters used as non-hierarchical cache keys.

route-on: Rails fragment caching uses the the current route to build its cache key. If you are caching an item that will be used in multiple different actions, you can specify route-on to ensure that the different actions can share the cache. You can pass either an object or a path to route-on. If you pass an object, it is converted to a path with url_for. If you specify route-on without a value, this is used. An alternative to using route-on is to specify controller, action and any other required path parameters explicitly. For example route-on=”&posts_path” is identical to controller=”posts” action=”index”. If you do not specify route-on or controller, action, etc., params[:page_path] or the current action is used. route-on is a non-hierarchical key.

Hints

Any Hobo tag that can generate a page refresh, such as filter-menu, table-plus or page-nav stores query parameters such as search, sort & page so that these are not lost when the tag is used to refresh the page. These will need to be added to the query-params for any cache containing such tags.

Example: Caching @current_user

This is the pattern we use to cache the page header:

 <swept-cache suffix="header" current-user="&@current_user.id" dependencies="&[@current_user]">

Notice that @current_user is specified as a dependency in 2 different ways.

current-user="&@current_user.id" ensures that the current user’s ID is added to the cache key, ensuring that each user gets their own header rather than having every user share the same header.

dependencies="&[@current_user] does not affect the cache key, only the cache content. It enables the cache to be swept if any of the user columns change, such as the user’s name.

Cache Store requirements

stability

For this tag to function correctly, the cache must not evict the dependency information, so purely LRU caches such as MemoryStore may not be used in production. The cache can evict fragments, though. For this reason, you may configure two separate caches:

config.cache_store = :memory_store, {:size => 512.megabytes}
config.hobo.stable_cache_store = :file_store

(If config.hobo.stable_cache_store is not specified, config.cache_store is also used for the stable cache store.)

Note that the dependency cache store does not have to be persistent, it may be cleared at any time as long as the cache store is also cleared at the same time.

If your chosen cache supports FIFO eviction it can be safely used, as long as you leave config.hobo.stable_cache_store undefined so that the fragment and dependency caches share the FIFO cache.

atomic updates

In production, swept-cache needs to be able to update a list atomically. This is not an operation supported by the Rails cache API, but it is supported by most non-trivial caches via one of several mechanisms, either through list support (Redis), and some caches support transactions (Infinispan).

consistent

If a cache for a single process is updated, the caches for all processes must also be updated.

supported caches

memory_store: not compliant, but can be used for development if the size is set large enough to avoid evictions.

file_store: A good choice for low traffic sites where reads vastly outnumber writes.

memcached: not compliant

redis: a great choice. You can use the same instance for both fragment caching with expiry set the fragments to expire without disturbing the dependency information by setting the options differently:

config.cache_store = :redis_store, "redis://192.168.1.2:6379/1", :expires_in => 60.minutes
config.hobo.stable_cache_store = :redis_store, "redis://192.168.1.2:6379/1"

(Note: Redis not yet tested. Email the hobousers list for a status update)

torquebox infinispan: another great choice

config.cache_store = :torque_box_store
config.hobo.stable_cache_store = :torque_box_store, :name => 'dependencies', :mode => :replicated, :sync => true

ehcache: can either use two differently configured caches in a manner similar to infinispan, or can use a single FIFO cache. Not yet tested. Email the hobousers list for a status update.

mongo: can be used in FIFO mode (aka capped collections). Not yet tested. Email the hobousers list for a status update.

others: ask on the hobo-users list.

Show Source


Edit this page