Making sense of my notes
Unfortunately the documentation for this new caching support seems quite scarce and unusually Google couldn’t help much either as there hasn’t been much blogging about it yet. So, not for the first time, I spent a bit of time digging into the source code to find out how it all fits together. What follows is a write-up of my hastily scribbled notes.
The basics
The code lives in the ActiveSupport::Cache module and it defines a base Store class which provides the basic interface for a caching store with methods like: read, write, delete and exist? (why oh why isn’t it exists? like ActiveRecord?). There is also a ThreadSafety module that wraps read/write operations in a Mutex#synchronize block. Enabling thread safety on a cache store is a simple matter of calling the threadsafe! method.
Rails includes five cache stores:
- File store (
ActiveSupport::Cache::FileStore) - uses individual files to store cached values. - Memory store (
ActiveSupport::Cache::MemoryStore) - uses a simple hash to store values. - DRb store (
ActiveSupport::Cache::DRbStore) - works just like theMemoryStorebut uses a DRb server to store values. - Memcached store (
ActiveSupport::Cache::MemCacheStore) - uses memcache and memcache-client (version 1.5.0 is bundled with Rails so there’s no need to install the Gem separately) to store values in a shared memory cache. - Compressed memcached store (
ActiveSupport::Cache::CompressedMemCacheStore) - works just like the regularMemCacheStorebut uses GZip to decompress/compress on read/write.
Rails automatically creates a global cache during initialisation and you can access this in your code either using the RAILS_CACHE global variable or the (in my opinion nicer looking) Rails.cache. Here are a few simple examples:
# Write a string to the cache
Rails.cache.write('test_key', 'test_value')
=> true
# Read it back in
Rails.cache.read('test_key')
=> 'test_value'
# Delete the item
Rails.cache.delete('test_key')
=> true
By default if the tmp/cache directory exists in your application root then Rails will use a FileStore otherwise it’ll use a MemoryStore as the global cache. Either of these options should be fine for development and testing, but for production you’ll almost certainly want to consider using the MemCacheStore. One of the big advantages of this new code is that for the most part it allows you to switch cache store without having to change any of your code.
Important! As Peter points out in the comments, this behaviour is not currently implemented (the code is there but not used) - I’ve submitted a bug report
Changing the global cache store
Rails provides the config.cache_store option in environment.rb to allow you to change the cache store. It accepts either a class name or symbol for the required store and an array of options used when initialising the store. Some examples:
# Use the memory store - this store has no options
config.cache_store = :memory_store
# Use the file store with a custom storage path (if the directory doesn’t already exist it will be created)
config.cache_store = :file_store, '/my_cache_path'
# Use the memcached store with default options (localhost, TCP port 11211)
config.cache_store = :mem_cache_store
# Use the memcached store with an options hash
config.cache_store = :mem_cache_store, 'localhost', '192.168.1.1:1001', { :namespace => 'test' }
Behind the scenes this makes use of the ActiveSupport::Cache.lookup_store method, which you can also call directly to create a cache of your own:
# Create a separate memory cache in addition to the global Rails cache
my_cache = ActiveSupport::Cache.lookup_store(:memory_cache)
Data keys
All of these cache stores provide what is essentially a Hash that can be read from and written to using a key. Just like with a regular Hash object, this key needs to be unique and not just within the scope of your application but also, in the case of shared caches like memcached, across all applications that are using the cache. To make cache key generation a little easier Rails does two things:
- Adds a cache_key method to
ActiveRecord::Basethat generates a key using the class name, record ID andupdated_attimestamp (if available). - Provides the
ActiveSupport::Cache.expand_cache_keymethod that builds a key using an optional namespace passed as a parameter to the method, theRAILS_CACHE_IDorRAILS_APP_VERSIONenvironment variables (if they are set) and the result of a call to thecache_keyorto_parammethod of the object being cached (if it responds to either of them) or in the case of anArraytheexpand_cache_keymethod is called for each element of the array and the resulting keys joined to form a single (potentially very long!) key.
MemCacheStore specifics
The MemCacheStore accepts an array of server addresses in the form hostname[:port][:weight]. For example, if you have two servers on different ports you could specify them like so:
config.cache_store = :mem_cache_store, '192.168.1.1:11000', '192.168.1.2:11001'
It also accepts a hash of additional options:
:namespace- specifies a string that will automatically be prepended to keys when accessing the memcached store.:readonly- a boolean value that when set to true will make the store read-only, with an error raised on any attempt to write.:multithread- a boolean value that adds thread safety to read/write operations - it is unlikely you’ll need to use this option as the Railsthreadsafe!method offers the same functionality.
The read and write methods of the MemCacheStore accept an options hash too. When reading you can specify :raw => true to prevent the object being marshaled (by default this is false which means the raw value in the cache is passed to Marshal.load before being returned to you.
When writing to the cache it is also possible to use the :raw option which when false (the default) means the value is passed to Marshal.dump before being stored in the cache. The write method also accepts an :unless_exist flag which determines whether the memcached add (when true) or set (when false) method is used to store the item in the cache and an :expires_in option that specifies the time-to-live for the cached item in seconds. A few examples:
# Write a raw value to the cache
Rails.cache.write('test_key', 1, :raw => true)
# Read a raw value from the cache
Rails.cache.read('test_key', :raw => true)
# Write a value using the add method and setting expiry to 15 minutes
Rails.cache.write('test_key', 'test_value', :unless_exist => true, :expires_in => 15.minutes)
Increment and decrement
As caches are often used to store counters all of the cache stores provide increment and decrement methods. For example:
# Store an initial counter value in the cache
Rails.cache.write('number_of_cakes_eaten', 1)
# Increment the counter by 5
Rails.cache.increment('number_of_cakes_eaten', 5)
This encapsulates the process of retrieving a value, incrementing or decrementing it and then storing the modified value back in the cache. In addition, the MemCacheStore takes advantage of memcached atomic increment/decrement functionality to perform this action in a single call to the cache.
Custom caches
You now know enough about Rails caching to make good use of it in your own code. If you want to take things a step further and implement your own custom cache store you might want to take a look at Ryan Daigle’s example - his blog entry also demonstrates how to configure ActionController caching in Rails 2.1.
And finally…running memcached on Windows (groan!)
As a long suffering Windows developer you may well have read all of this, gotten quite excited about using memcached, and then realised that you’ve got to get it running on Windows. It’s normally at this point that I end up getting annoyed and resolve to buy a Mac, but I’m pleased to say on this occasion I was pleasantly surprised. I simply did the following:
- Downloaded this Win32 port of memcached by Kenneth Dalgleish.
- Extracted the files to C:\Program Files\memcached
- Opened a command prompt in the folder and installed memcached as a service using memcached -d install
- Started the service using net start "memcached Server"
And, shockingly, it just worked!


25 comments
Comment on Rails 2.1: now with better integrated caching by Mitchell Hashimoto
June 10th, 2008 @ 01:17 – permalink
Comment on Rails 2.1: now with better integrated caching by Piyush
June 10th, 2008 @ 04:20 – permalink
Comment on Rails 2.1: now with better integrated caching by Jim Neath
June 18th, 2008 @ 09:55 – permalink
Comment on Rails 2.1: now with better integrated caching by Ryan Bates
June 19th, 2008 @ 16:39 – permalink
Comment on Rails 2.1: now with better integrated caching by jeff
June 19th, 2008 @ 20:54 – permalink
Comment on Rails 2.1: now with better integrated caching by Peter Wagenet
June 27th, 2008 @ 21:23 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
June 30th, 2008 @ 11:57 – permalink
Comment on Rails 2.1: now with better integrated caching by Joel Nylund
July 8th, 2008 @ 03:04 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
July 8th, 2008 @ 09:35 – permalink
Comment on Rails 2.1: now with better integrated caching by Peter Abrahamsen
July 9th, 2008 @ 17:22 – permalink
Comment on Rails 2.1: now with better integrated caching by Joel Nylund
July 9th, 2008 @ 22:04 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
July 10th, 2008 @ 08:46 – permalink
Comment on Rails 2.1: now with better integrated caching by Jochen
July 11th, 2008 @ 21:33 – permalink
Comment on Rails 2.1: now with better integrated caching by Russ Smith
July 25th, 2008 @ 04:24 – permalink
Comment on Rails 2.1: now with better integrated caching by Matt Hodgson
July 30th, 2008 @ 00:48 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
August 12th, 2008 @ 08:54 – permalink
Comment on Rails 2.1: now with better integrated caching by Nathan Leavitt
August 14th, 2008 @ 19:31 – permalink
Comment on Rails 2.1: now with better integrated caching by Wes
August 15th, 2008 @ 19:39 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
August 16th, 2008 @ 18:55 – permalink
Comment on Rails 2.1: now with better integrated caching by karmi
August 19th, 2008 @ 10:47 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
August 20th, 2008 @ 08:56 – permalink
Comment on Rails 2.1: now with better integrated caching by hosiawak
September 3rd, 2008 @ 17:26 – permalink
Comment on Rails 2.1: now with better integrated caching by Chirantan
February 20th, 2009 @ 06:54 – permalink
Comment on Rails 2.1: now with better integrated caching by Rob Anderton
February 20th, 2009 @ 12:18 – permalink
Comment on Rails 2.1: now with better integrated caching by Joe Ellis
March 27th, 2009 @ 01:12 – permalink
Leave a reply
You can use Markdown in your comment as well as plain HTML. You can use
<filter:jscode lang="ruby">and</filter:jscode>tags to surround code blocks (supported languages are css, html, javascript and ruby). Your email address will not be published.If your comment doesn’t appear immediately after posting it could have been marked as spam. Don’t worry: we regularly check for and approve incorrectly filtered comments so you shouldn’t have to wait too long for it to be shown.