Friday, February 5, 2010

Rails cache expiration with sweepers



Sweepers enable expiring or deleting the rails cached items from a centralized place.This exempt us from the pain of writing cache expiration codes scattered throughout the code base by providing an implementation of the observer pattern.Sweeper classes observe one or more models that contributes to the cache data and provide options to expire the related cached items when any changes occur in those models(created,updated or deleted).

Let's understand the process with an example.We have a model named "SystemMessage" that provides messages posted by the system administrator.In the main layout we display the count of total system messages.Since several views are rendered inside this layout, we need to hit the database every time to get the count.To avoid this we decide to cache the count during the first database hit.We cache the count in the following code placed inside a method of the application_controller.

Rails.cache.fetch("systen_message_count") {SystemMessage.count}


The code above hits the database to fetch the count at the very first access and cache it with the key "systen_message_count". Subsequent calls to the method returns the count from the cache until it is expired.

Now we need to expire and reload the count when a new "SystemMessage" is created or an existing one is deleted.Instead of putting the cache expiration code in different places inside controllers and models, we can put it inside a single sweeper class and react to the changes in the "SystemMessage" model(creation or deletion).Here is the code placed inside the "\app\models\cache\system_messages_sweeper.rb" file.

class SystemMessagesSweeper < ActionController::Caching::Sweeper

observe SystemMessage

def after_create(record)
 expire_system_message_cache(record)
end

def after_destroy(record)
 expire_system_message_cache(record)
end

private

def expire_system_message_cache(record)
  Rails.cache.delete("systen_message_count")
end

end

In the code above, we observe the "SystemMessage" model and expire the system_message_count" cache item when a new record is created or deleted.So the count
will be loaded again from the database at the next hit and will be cached subsequently.

The sweeper class needs to be loaded by the rails.So we put the following code into the environment.rb file

config.load_paths += %W( #{RAILS_ROOT}/app/models/cache )


The sweeper has to be added to the controller that will use it. The following code is added to the "SystemMessagesController" class.

cache_sweeper :system_messages_sweeper, :only => [ :create,destroy ]


Rails cache sweepers have played an important role in improving the performance of ScrumPad, which is a popular agile/scrum project management and collaboration tool.