Enabling MemCached sessions

Rails has been able to store session data in MemCached since version 2.0 was released. I’ve been able to make it work locally, but never in production. Until now that is. I’ll show you the really simple trick to make it work.

Every time I tried to deploy my application, I got an error message saying that Rails couldn’t talk to the memcache servers. I checked and double checked the server setup and I knew everything was working. It wasn’t a big deal to us, so I just went back to the ActiveRecord store. Recently, we’ve been pushing the limits of our DB servers and I figure eliminating a query and an update per page view can only help. It was finally time to solve this problem.

When I’m stuck with the type of problem, I normally take a look at the source code. Within a couple of minutes, I found my problem:

        def initialize(session, options = {})
          id = session.session_id
          unless check_id(id)
            raise ArgumentError, "session_id '%s' is invalid" % id
          end
          @cache = options['cache'] || MemCache.new('localhost')

The MemCacheStore expects you to pass in a cache object during configuration. I assumed (incorrectly) that it would use the same connection as cache-fu. In an attempt to be helpful, Rails was silently trying to connect to memcached on the local host. Convention over configuration can be nice, but it’s helpful to at least document these types of things. There is great documentation in the code itself, but unfortunately, this class isn’t documented on http://api.rubyonrails.com/.

Now that we know our problem, fixing it is simple:

require 'memcache'

Rails::Initializer.run do |config|
  require "hodel_3000_compliant_logger"
  config.logger = Hodel3000CompliantLogger.new(config.log_path)

  if RAILS_ENV =~ /production/
    config.action_controller.session_store = :mem_cache_store
    config.action_controller.session = { 
      :session_key => "_facebook_gifts_#{RAILS_ENV}", 
      :cache => MemCache.new(["server1.elevatedrails.com","server2.elevatedrails.com"])
    }
  else
    config.action_controller.session_store = :active_record_store
  end

The application in question runs under 4 different installs, each with a different environment name. The above change allows me to include the code for setup only once. With that, I can eliminate 15mm rows of data and one query per page view.