Rails MemcachedStore for the Memcached Gem 1

Posted by acts_as_flinn Sun, 26 Oct 2008 01:55:00 GMT

11

I’ve been experimenting with the memcached gem (the c gem that runs up to 150x faster than memcache-client). Merb already has pretty good support for the Memcached gem and in typical merb style does it better with fewer lines of code but I digress. Here’s my test implementation, so far so good nothing really heavy on it yet. YMMV so be warned this is not tested in a production environment don’t say I didn’t warn you so and such.


# in your environment file
config.cache_store = :memcached_store, 'localhost:11211'

# lib/memcached_store.rb
require 'memcached'

module ActiveSupport
  module Cache
    class MemcachedStore < Store
      attr_reader :addresses

      def initialize(*addresses)
        addresses = addresses.flatten
        options = addresses.extract_options!
        addresses = ["localhost"] if addresses.empty?
        @addresses = addresses
        @data = Memcached::Rails.new(addresses, options)
      end

      def read(key, options = nil)
        super
        @data.get(key, raw?(options))
      rescue Memcached::Error => e
        logger.error("Memcached::Error (#{e}): #{e.message}")
        nil
      end

      # Set key = value. Pass :unless_exist => true if you don't 
      # want to update the cache if the key is already set. 
      def write(key, value, options = nil)
        super
        method = options && options[:unless_exist] ? :add : :set
        response = @data.send(method, key, value, expires_in(options), raw?(options))
        response == nil
      rescue Memcached::Error => e
        logger.error("Memcached::Error (#{e}): #{e.message}")
        false
      end

      def delete(key, options = nil)
        super
        response = @data.delete(key, expires_in(options))
        response == nil
      rescue Memcached::Error => e
        logger.error("Memcached::Error (#{e}): #{e.message}")
        false
      end

      def exist?(key, options = nil)
        # Doesn't call super, cause exist? in memcache is in fact a read
        # But who cares? Reading is very fast anyway
        !read(key, options).nil?
      end

      def increment(key, amount = 1)       
        log("incrementing", key, amount)

        @data.incr(key, amount)  
      rescue Memcached::Error
        nil
      end

      def decrement(key, amount = 1)
        log("decrement", key, amount)

        @data.decr(key, amount) 
      rescue Memcached::Error
        nil
      end        

      def delete_matched(matcher, options = nil)
        super
        raise "Not supported by Memcache" 
      end        

      def clear
        @data.flush_all
      end        

      def stats
        @data.stats
      end

      private
        def expires_in(options)
          (options && options[:expires_in]) || 0
        end

        def raw?(options)
          options && options[:raw]
        end
    end
  end
end
Snippet

Thoughts on User.current and Thread Safety 9

Posted by acts_as_flinn Sun, 26 Oct 2008 01:38:00 GMT

A thimble

Pratik Naik has an interesting post Thread safety for your Rails about what thread safety in Rails means (and what it doesn’t). One of the interesting points is that a common pattern of using class attributes is not thread safe and can lead to race conditions. I found it interesting because I’m a big proponent of User.current. I’ve always gotten great millage out of it and I’ve always felt like it’s a necessary evil for protecting user_id attributes from mass assignment and not totally driving yourself nuts with @foo.user_id = session[:user_id] all over your controller actions.

So the problem now is that if you ever intend to use User.current with a thread safe Rails app you’ve got to adjust fire. My thoughts are now turned toward investigating the following.


class ApplicationController < ActionController::Base
 before_filter :current_user_id

 # Set the value
 def current_user_id
   Thread.current[:current_user_id] = session[:user_id]
 end
end

class User < ActiveRecord::Base
 def self.current
   Thread.current[:current_user] ||= self.fetch_by_id(Thread.current[:current_user_id])
 end
end
Snippet

This seems like a thread safe method of accessing the current User without trading off the real functionality we want.