Wraps memcached, redis, memcache-client, dalli and handles their weirdnesses, including forking.
Aims to let other libraries be cache-agnostic in return for a performance hit.
Used by lock_method and cache_method so that you can use them with memcached, redis, etc.
In production use at carbon.brighterplanet.com and data.brighterplanet.com.
require 'memcached' # a really fast memcached client gem by Evan Weaver, one of the lead engineers at Twitter
require 'cache' # this gem, which wraps the client to provide a standard interface
client = Memcached.new('127.0.0.1:11211', :binary_protocol => true)
@cache = Cache.wrap(client)
# don't worry, even though it's memcached gem, this won't raise Memcached::NotFound
@cache.get('hello')
# fetch is not provided by the memcached gem, the wrapper adds it
@cache.fetch('hello') { 'world' }
# don't worry, the wrapper will automatically clone the Memcached object after forking (or threading for that matter)
Kernel.fork { @cache.get('hello') }
If you can't use the memcached gem (because you're on heroku, for example) then just wrap a dalli or a redis client. You still get exactly the same interface.
I wanted a common interface to a bunch of great Ruby cache clients so I can develop gems (lock_method, cache_method) that accept any of them.
- I'm tired of rescuing from Memcached::NotFound
- I'm tired of forgetting whether it's :expires_in or :ttl
- I don't know why we ever started using read/write instead of get/set.
- I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
- I don't know why Memcached::Rails isn't implemented as an ActiveRecord::Cache::Store (Dalli did it just fine!)
- Why are you asking me about :raw or whatever? Just marshal it
It's about 50% slower than raw Memcached (if that's what you're wrapping) and barely slower at all than Dalli (if that's what you're wrapping.)
user system total real
set: cache:dalli:bin 5.710000 1.870000 7.580000 ( 10.210710) <- Cache.wrap(Dalli::Client.new)
set: cache:libm:bin 1.320000 1.260000 2.580000 ( 5.913591) <- Cache.wrap(Memcached.new(:binary_protocol => true))
set: dalli:bin 5.350000 1.860000 7.210000 ( 9.860368) <- Dalli::Client.new
set: libm:ascii 0.760000 1.310000 2.070000 ( 5.369027)
set: libm:ascii:pipeline 0.280000 0.020000 0.300000 ( 0.300872)
set: libm:ascii:udp 0.640000 0.690000 1.330000 ( 3.618846)
set: libm:bin 0.640000 1.370000 2.010000 ( 5.287203) <- Memcached.new(:binary_protocol => true)
set: libm:bin:buffer 0.320000 0.170000 0.490000 ( 1.238471)
set: mclient:ascii 11.840000 3.820000 15.660000 ( 15.933338)
set: stash:bin 3.420000 1.300000 4.720000 ( 7.871299)
get: cache:dalli:bin 5.740000 2.050000 7.790000 ( 10.220809) <- Cache.wrap(Dalli::Client.new)
get: cache:libm:bin 1.330000 1.260000 2.590000 ( 5.789277) <- Cache.wrap(Memcached.new(:binary_protocol => true))
get: dalli:bin 5.430000 2.050000 7.480000 ( 9.945485) <- Dalli::Client.new
get: libm:ascii 0.970000 1.290000 2.260000 ( 5.421878)
get: libm:ascii:pipeline 1.030000 1.590000 2.620000 ( 5.728829)
get: libm:ascii:udp 0.790000 0.730000 1.520000 ( 3.393461)
get: libm:bin 0.830000 1.330000 2.160000 ( 5.362280) <- Memcached.new(:binary_protocol => true)
get: libm:bin:buffer 0.900000 1.640000 2.540000 ( 5.719478)
get: mclient:ascii 14.010000 3.860000 17.870000 ( 18.125730)
get: stash:bin 3.100000 1.320000 4.420000 ( 7.559659)
Thanks to https://github.com/fauna/memcached/blob/master/test/profile/benchmark.rb
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
For example, you don't have to set up unicorn or PhusionPassenger's after_fork.
0 means don't expire.
The default ttl is 60 seconds.
Everything gets marshalled. No option to turn it into "raw" mode. If you need that kind of control, please submit a patch or just use one of the other gems directly.
It will translate these methods to whatever Redis, Memcached, etc. client you're using:
@cache.get 'hello'
@cache.set 'hello', 'world', 5.minutes
@cache.delete 'hello'
@cache.flush
@cache.exist? 'hello'
@cache.reset
@cache.fetch 'hello' { 'world' }
@cache.cas 'hello' { |current| 'world' }
@cache.increment 'high-fives'
@cache.decrement 'high-fives'
@cache.get_multi 'hello', 'privyet', 'hallo'
Also provided for Rails compatibility:
@cache.write 'hello', 'world', :expires_in => 5.minutes
@cache.read 'hello'
@cache.clear
@cache.compare_and_swap
@cache.read_multi 'hello', 'privyet', 'hallo'
Supported memcached clients:
- memcached (native C extensions, super fast!)
- dalli (pure ruby, recommended if you're on heroku)
- memcache-client (not recommended. the one that comes with Rails.)
Supported Redis clients:
Super-fast memcached | Pure Ruby memcached (works on Heroku) | Redis | |
---|---|---|---|
Rails | config.cache_store = Cache.wrap(Memcached.new) |
config.cache_store = Cache.wrap(Dalli::Client.new) |
config.cache_store = Cache.wrap(Redis.new) |
Your own library |
# Accept any client, let Cache take care of it def cache=(raw_client) @cache = Cache.wrap(raw_client) end |
# Accept any client, let Cache take care of it def cache=(raw_client) @cache = Cache.wrap(raw_client) end |
# Accept any client, let Cache take care of it def cache=(raw_client) @cache = Cache.wrap(raw_client) end |
CacheMethod (already uses Cache internally) | CacheMethod.config.storage = Memcached.new |
CacheMethod.config.storage = Dalli::Client.new |
CacheMethod.config.storage = Redis.new |
LockMethod (already uses Cache internally) | LockMethod.config.storage = Memcached.new |
LockMethod.config.storage = Dalli::Client.new |
LockMethod.config.storage = Redis.new |
It defaults to an in-process memory store:
@cache = Cache.new
@cache.set 'hello'
@cache.get 'hello', 'world'
You can specify a more useful cache client:
require 'redis' # the redis key-value store
require 'cache' # this gem, which provides a standard interface
raw_client = Redis.new
@cache = Cache.wrap(raw_client)
or
require 'dalli' # the dalli memcached client used by heroku
require 'cache' # this gem, which provides a standard interface
raw_client = Dalli::Client.new
@cache = Cache.wrap(raw_client)
Or you could piggyback off the default rails cache:
@cache = Cache.wrap(Rails.cache)
Copyright 2011 Seamus Abshere