Get your Caffeine on

I recently stumbled upon Adocca plugins, which is a suite of plugins / extensions extracted from the development of trig.com.

This includes a nifty Ruby extension, Caffeine, built on libmemcache, the C API for Memcached.The API is almost 100% compatible with memcache-client, with the following exceptions :

  • No get_multi ( may or may not be a big deal for you )
  • Multithreaded by default, only accepts a single :namespace as options Hash
  • MemCache#servers is write only and you can't overwrite previously defined servers
  • MemCache#get accepts either a String or Array, the latter which forces a namespace lookup (more on this later)

I'll continue with a quick rundown ...

  • Example installation ( OS X )
  • Requires a properly configured Memcached environment

Libmemcache installation

Grab it here, extract and ....


libmemcache-1.4.0.rc2 lourens$ ./configure && make && make install

Libuuid installation

Download the e2fsprogs package, extract and ...


e2fsprogs-1.39 lourens$ ./configure
e2fsprogs-1.39 lourens$  cd lib/uuid
e2fsprogs-1.39 lourens$ make && sudo make install

We don't need the whole package, just libuuid.

Caffeine installation


lourens$ sudo gem install caffeine
Building native extensions.  This could take a while...
caffeine.c: In function 'key_from_segments':
caffeine.c:712: warning: incompatible implicit declaration of built-in function 'mempcpy'
caffeine.c: In function 'md5_digest':
caffeine.c:872: warning: pointer targets in passing argument 1 of 'MD5' differ in signedness
/usr/bin/ld: warning multiple definitions of symbol _setregid
/opt/local/lib/libruby.dylib(process.o) definition of _setregid
/usr/lib/gcc/i686-apple-darwin8/4.0.1/../../../libpthread.dylib(setregid.So) definition of _setregid
/usr/bin/ld: warning multiple definitions of symbol _setreuid
/opt/local/lib/libruby.dylib(process.o) definition of _setreuid
/usr/lib/gcc/i686-apple-darwin8/4.0.1/../../../libpthread.dylib(setreuid.So) definition of _setreuid
ruby extconf.rb install caffeine
checking for mc_new() in -lmemcache... yes
checking for uuid_generate() in -luuid... yes
checking for main() in -lssl... yes
creating Makefile

make
gcc -I. -I. -I/opt/local/lib/ruby/1.8/i686-darwin8.9.1 -I. -O -pipe -I/opt/local/include -fno-common -ggdb -O0 -Df  -c caffeine.c
cc -dynamic -bundle -undefined suppress -flat_namespace -L/opt/local/lib   -L"/opt/local/lib" -o caffeine.bundle caffeine.o  -lruby -lssl -luuid -lmemcache  -lpthread -ldl -lobjc -lspread 

make install
/usr/bin/install -c -m 0755 caffeine.bundle /opt/local/lib/ruby/gems/1.8/gems/caffeine-0.0.2/lib

make clean
Successfully installed caffeine-0.0.2

Namespaces

The interesting aspect about this client lib is the elegant namespace support, not to be confused with your connection namespace.One could have keys like the following (pseudo code) :

  • bookings-development:Account:1:Addresses:2
  • bookings-development:Account:20:Addresses:12

Very simple to sweep a whole set of related keys eg. to expire Account information, we just unlink the Account namespace.Actual keys resemble this ...

  • bookings-development:7f722ed3a969554d010e62510217cc

... courtesy of libuuid to avoid namespace / key trashing.

Performance?

I didn't gain all that much by using Caffeine in my dev / staging environment, but mileage varies, I like to keep good karma and thus won't open a 'scaling / performance' can of worms.

Integration

The following svn diff applied to cache_fu revision 240 should have you up and running without hickups.


lourens$ svn diff vendor/plugins/cache_fu
Index: vendor/plugins/cache_fu/lib/acts_as_cached/config.rb
===================================================================
--- vendor/plugins/cache_fu/lib/acts_as_cached/config.rb        (revision 2337)
+++ vendor/plugins/cache_fu/lib/acts_as_cached/config.rb        (working copy)
@@ -42,9 +42,16 @@

     def setup_memcache(config)
       config[:namespace] << "-#{RAILS_ENV}"
-
+      
+      begin
+        require 'caffeine'
+        Caffeine::MemCache.class_eval{ attr_reader :namespace  }
+      rescue          
+      end
+                 
       silence_warnings do
-        Object.const_set :CACHE, MemCache.new(config)
+        Object.const_set :CACHE, Object.const_defined?( :Caffeine ) ? Caffeine::MemCache.new( { :namespace => config[:namespace] } ) : MemCache.new(config) 
+                                 
       end

       CACHE.servers = Array(config.delete(:servers))
Index: vendor/plugins/cache_fu/lib/acts_as_cached/cache_methods.rb
===================================================================
--- vendor/plugins/cache_fu/lib/acts_as_cached/cache_methods.rb (revision 2337)
+++ vendor/plugins/cache_fu/lib/acts_as_cached/cache_methods.rb (working copy)
@@ -26,7 +26,7 @@
         id = args.first
       end

-      if (item = fetch_cache(id)).nil?
+      if (item = fetch_cache(id)).nil? || item == :MemCache_no_such_entry
         set_cache(id, block_given? ? yield : fetch_cachable_data(id), options[:ttl])
       else
         item
@@ -239,7 +239,7 @@
       set_cache
     end

-    # Lourens Naud?
+    # Lourens Naudé
     def expire_cache_with_associations(*associations_to_sweep)
       ((cache_options[:include] || []) + associations_to_sweep).flatten.uniq.compact.each do |assoc|
         Array(send(assoc)).compact.each { |item| item.expire_cache if item.respond_to?(:expire_cache) }


About this entry