memcache

This is the Geni memcached client. It started out as a fork of
fiveruns/memcache-client, which was a fork of seattle.rb's
memcache-client, but over time, our client has diverged, and I've
rewritten the majority of the code. Of course, a lot of credit is due to
those whose code served as a starting point for this code. Also, thanks to
Evan Weaver for the idea to include the libmemcached source and the code to
make it compile when installing the gem.

How is this different from other memcache clients?

Like fiveruns/memcache-client and fauna/memcached, memcache (shown
in italics when I am referring to this library) is a memcached client, but
it differs significantly from these clients in several important ways.

Interface

I tried to keep the basic interface as similar as I could to
memcache-client. In some cases, memcache can be a near drop-in
replacement for memcache-client. However, I did rename the main class from
MemCache to Memcache to prevent confusion and to force
those switching to memcache to update their code. Here are the
notable interface changes:

expiry and raw are specified as options in a hash now,
instead of as unnamed parameters.

get_multi has been replaced by a more versatile get
interface. If the first argument is an array, then a hash of key/value
pairs is returned. If the first argument is not an array, then the value
alone is returned.

get also supports updating the expiry for a single key. this can
be used to keep frequently accessed data in cache longer than less accessed
data, though usually the memcached LRU algorithm will be sufficient.

cache.get('foo',:expiry=>1.day)

Support for flags has been added to all methods. So you can store
additional metadata on each value. Depending on which server version you
are using, flags can be 16 bit or 32 bit unsigned integers (though it seems
that memcache 1.4.1 returns signed values if the upper bit is set).

Implementation

The underlying architechture of memcache is more modular than
memcache-client. A given Memcache instance has a group of servers,
just like before, but much more of the functionality is encapsulated inside
the Memcache::Server object. Really, a Server object is a
thin wrapper around an remote memcached server that takes care of the
socket and protocol details along with basic error handling. The
Memcache class handles the partitioning algorithm, marshaling of
ruby objects and various higher-level methods.

By encapsulating the protocol inside the Server object, it becomes
very easy to plug-in alternate backend server implementations. Right now,
there are three basic, alternate servers:

LocalServer

This is an in-process server for storing keys and values in local memory.
It is good for testing, when you don't want to spin up an instance of
memcached, and also as a second level of caching. For example, in a web
application, you can use this as a quick cache which lasts for the duration
of a request.

PGServer

This is an implementation of memcached functionality using SQL. It stores
all data in a single postgres table and uses PGConn to select and
update this table. This works well as a permanent cache or in the case when
your objects are very large. It can also be used in a multi-level cache
setup with Memcache::Server to provide persistence without
sacrificing speed.

NativeServer

This implementation uses native bindings to libmemcached. It is described
in more detail in the “Native Bindings” section below.

Very Large Values

Memcached limits the size of values to 1MB. This is done to reduce memory
usage, but it means that large data structures, which are also often costly
to compute, cannot be stored easily. We solve this problem by providing an
additional server called Memcache::SegmentedServer. It inherits
from Memcache::Server, but includes code to segment and reassemble
large values. Mike Stangel at Geni originally wrote this code as an
extension to memcache-client and I adapted it for the new architecture.

You can use segmented values either by passing SegmentedServer
objects to Memcache, or you can use the
segment_large_values option.

Error Handling and Recovery

We handle errors differently in memcache than memcache-client
does. Whenever there is a connection error or other fatal error,
memcache-client marks the offending server as dead for 30 seconds, and all
calls that require that server fail for the next 30 seconds. This was
unacceptable for us in a production environment. We tried changing the
retry timeout to 1 second, but still found our exception logs filling up
with failed web requests whenever a network connection was broken.

So, the default behavior in memcache is for reads to be stable
even if the underlying server is unavailable. This means, that instead of
raising an exception, a read will just return nil if the server is down. Of
course, you need to monitor your memcached servers to make sure they
aren't down for long, but this allows your site to be resilient to
minor network blips. Any error that occurs while unmarshalling a stored
object will also return nil.

Writes, on the other hand, cannot just be ignored when the server is down.
For this reason, every write operation is retried once by closing and
reopening the connection before finally marking a server as dead and
raising an exception. We will not attempt to read from a dead server for 5
seconds, but a write will always attempt to revive a dead server by
attempting to connect.

Keys, Namespaces, and Prefixes

Unlike the other ruby memcache clients, keys in memcache can
contain spaces. This is possible because the backend transparently enscapes
all space characters, and is especially important if you are using method_cache or record_cache.
Memcache::Server implements this escaping using gsub and it adds a
slight performance penalty when escaping is necessary.
NativeServer implements this escaping directly in C, and the
performance overhead is negligible.

You can also partition your keys into different namespaces for convenience.
This is done by prefixing all keys in the backend server with “namespace:”.
However, the hash keys returned by multi gets do not contain the prefix. In
this way, the namespace can be totally transparent to your code. You can
also determine whether the prefix is used for hashing with the following
option:

hash_with_prefix

Determines whether the prefix/namespace is used when hashing keys to
determine which server to use. Defaults to true.

Native Bindings

The Memcache::NativeServer backend provides native bindings to
libmecached. This is significantly faster than using
Memcache::Server as demonstrated by runnning bench/benchmark.rb.
NativeServer encapsulates a set of remote servers and allows you to use the
various hashing methods in libmemcached.

You can use native bindings either by passing NativeServer objects
to Memcache, or you can use the native option. Native
bindings are compatible with segmented values through the
SegmentedNativeServer object or by combining the native
option with segment_large_values.