cachecataz

Cachecataz is for namespace expiry in a cache where the cache provider does not enable namespace based expiry (like memcached).

Cachecataz creates a namespace key based on a cache_scope defined for the class. Each cache namespace can be expired through an instance or the class.

Problem

Cache servers like Memcache only provide expiry for cache keys based on a unique value. Don’t allow you to specify an entire namespace to expire based on changes in the underlying data.

Solution

Store a value in the cache that determines the namespace of the cache key and increment the value of the namespace key each time the namespace needs to be expired.

Using Cachecataz

Install it:

gem install cachecataz

Include it and define your scopes:

class Element < ActiveRecord::Base
  include Cachecataz

  cache_scope :name, [:user_id]
end

Enable it:

# ** production.rb **
Name::Application.configure do
  #....... bunch of stuff

  config.action_controller.perform_caching = true
  config.cache_store = :mem_cache_store, 'localhost:11211'

  # .... more stuff

  config.after_initialize do
    Cachecataz.enable = true
    Cachecataz.provider = Rails.cache
  end
end

Cachecataz is disabled by default. If you use it outside of Rails you need to enable it through the configuration options. Inside Rails this is done in your environment files under your Rails.root/config/environments/ dir.

A cache_scope is the mechanism to create a unique scope for a namespace. The namespace key will be comprised of the cache_scope name (in this example “name”) and the runtime state of any value passed in the Array of symbols that make up a unique scope.

For the example above the namespace key would be “name:#elementelement.user_id”, which allows us to have a unique namespace for each “name” key scoped by the user_id.

Example (in Rails)

Basic premise: An Element belongs_to a User and a Widget belongs_to a User as well. The “something” partial displays data primarily related to the Element but also displays data from the related Widget. I want to have a sweeper that observes changes in the Widget model and expires the namespace for the Element. (can be kinda confusing, but makes sense in the context of a cache that is model dependent.)

Generate a cache key:

# ** Model **
class Element < ActiveRecord::Base
  include Cachecataz

  cache_scope :user, [:user_id]
end

** View **
<% cache(@element.cache_key(:user, :id)) do %>
  <% render :partial => "something", :locals => { :widget => @element.widget } %>
<% end %>

# ** Observer **
class WidgetObserver < ActiveRecord::Observer
  after_update(widget)
    Element.expire_namespace(:user, widget.attributes)  # can also just pass {:user_id => widget.user_id} as the scope_hash, scope requires :user_id
  end
end

# ** Another Observer **  Why expire_fragment is available? not important
class ElementObserver < CacheSweepingObserver
  after_update(element)
    expire_fragment(element.cache_key(:user, :id))  # Will only expire a single key "0:user:1/12" if user_id == 1 and id == 12
  end

  after_create(element)
    element.expire_namespace(:user)   # will expire the entire namespace for cache_scope :user for user 1 if user_id ==1
  end
end

# by default the scope_hash on any expire_namespace call for an instance is self.attributes

TODO

post more info on how to use cachecataz with a cache that is not Rails.cache (it’s very simple), but wanted to get the basics released

Contributing to cachecataz

  • Check out the latest master to make sure the feature hasn’t been implemented or the bug hasn’t been fixed yet

  • Check out the issue tracker to make sure someone already hasn’t requested it and/or contributed it

  • Fork the project

  • Start a feature/bugfix branch

  • Commit and push until you are happy with your contribution

  • Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.

  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright © 2011 Brandon Dewitt. See LICENSE.txt for further details.