Let's cache some stuff!
0
Building a large-scale web application can be troublesome. It leaves us
facing all sorts of optimization problems. A very good – but not easy -
solution is to use caching. Caching is a real lifesaver as it can help with
minimizing i) database queries and ii) string operations.
These are the two main problems that can affect the performance of a web
application.
A common problem that developers always face is the need to call a method
regularly knowing that its results rarely change and what’s more, that the cost
is always high. More on that in the following code:
class User < ActiveRecord::Base def costy_method #some real heavy calculation that takes a lot of resources #to complete #this method is called regularly and its results rarely change end end
Calling user.costy_method with all its costs and behavior appears to be a
point that can be optimized. How about caching its result? The
user.costy_method needs to be modified so that it caches its result. A common
practice for Ruby developers is to cache the result in an instance variable the
following way
def costy_method @costy_method_result || = #the actual calculations end
What if there were more methods like this? They’ll all have to be rewritten
so as to cache their results. This would be a tedious task with a lot of
repeated code which violates the DRY concept. Why not create a simpler solution for this
particular problem? We wrote the method_cache plugin to do just that. Let’s see
how it works:
class User < ActiveRecord::Base def costy_method #some real heavy calculation that takes alot of resources #to complete #this method is called regularly and its results rarely change end caches_method :costy_method end
And voila, every time user.costy_method is called, you’ll get the value you
expect normally but this time at a much less cost.
caches_method :costy_method builds a 2 level cache(a note on
that later), it caches the result in an instance variable inside the user
object and it also saves a copy in your fragment_cache_store
which can be of several types (a file store, a memory store, or memcached). Now
if the same user instance is used to call user.costy_method we’ll get a huge
performance gain as the only thing it will cost you is the cost of retrieving
any instance variable. If another user instance is used, the first time you
call user.costy_method the value will be retrieved from your cache store and
put for your convenience in the instance variable. If the value can’t be
retrieved from an instance variable or the cache store the real costy_method
implementation is executed to get that value.
Another convenience method is expire_method :method_name which can be used
like user.expire_method :costy_method. This method will simply
clear the instance variable and cache store entry if they’re available so that
next time user.costy_method is called, a new value will be calculated.
Mido – a.k.a. Muhammed Ali – wrote
this plugin. I pretty much loved the simplicity by which this will allow us to
cache stuff.
More on the plugin features
We needed to be able to cache the results for a certain amount of time. The
problem is that the default MemCacheStore didn’t support that. So again with
some Ruby magic I overrode the MemCacheStore code so that I can write some
stuff like the following
caches_method :costy_method, :until => :midnight
or
caches_method :costy_method, :for => 20.days
After using the plugin internally for a while we decided it’s best if others
can make use of it and also contribute. We’ve setup a project on RubyForge which can be found here.
In its current form, the plugin provides the following extensions to ActiveRecord
Objects
caches_method :method_name
caches_class_method :method_name
and for expiring the cache
instance.expire_method :method_name
Class.expire_instance_method :method_name, id
Class.expire_class_method :method_name
More on the 2 level cache mentioned before: for class methods, we cached the
results in static variables but this was a deal breaker once we used several
mongrels to serve our application as it’s impossible to synchronize the expiry
of the static variables on the different mongrels. Ultimately, we decided it
was enough to use the fragment cache store as our only store when dealing with
class methods.
I think it would be nice to refactor the plugin so as to be able to use it
with all classes not just ActiveRecord::Base descendants. This would require
some minor changes such as defining a method that defines an instance uniquely.
In the case of ActiveRecord objects, it’s just the id.
Post a Comment
eSpace podcast Prodcast
Archive
- September 2011
- April 2011
- March 2011
- December 2010
- November 2010
- September 2010
- August 2010
- July 2010
- June 2010
- April 2010
- March 2010
- November 2009
- October 2009
- September 2009
- July 2009
- June 2009
- May 2009
- April 2009
- March 2009
- February 2009
- January 2009
- November 2008
- October 2008
- September 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- January 2008
- April 2007
- March 2007
Latest Comments
- SpectraMind Commented on Egypt Wins UK's National Outsourcing Association Award
- Rofaida Awad Commented on Go Egypt Go!
- Different Mike Commented on Only idiots change their iPhone root password!
- Mike Commented on Only idiots change their iPhone root password!
- smile Commented on Only idiots change their iPhone root password!

