Fixing documentation, adding initial FallbackClient impl

This commit is contained in:
Charles Gordon
2012-10-22 08:38:13 -07:00
parent 9cef80deb7
commit dd90d16a72
3 changed files with 206 additions and 45 deletions

View File

@@ -1,4 +1,63 @@
pymemcache
==========
A comprehensive, fast, pure-Python memcached client. BETA!
NOTE: this is still BETA, use with caution!
A comprehensive, fast, pure-Python memcached client.
pymemcache supports the following features:
* Complete implementation of the memcached text protocol.
* Configurable timeouts for socket connect and send/recv calls.
* Access to the "noreply" flag, which can significantly increase speed.
* Flexible, simple approach to serialization and deserialization.
* The (optional) ability to treat network and memcached errors as cache misses.
Installing pymemcache
=====================
You can install pymemcache manually, with Nose tests, by doing the following:
git clone https://github.com/pinterest/pymemcache.git
cd pymemcache
python setup.py nosetests
sudo python setup.py install
You can also use pip:
sudo pip install https://github.com/pinterest/pymemcache.git
Comparison with Other Libraries
===============================
pylibmc
-------
The pylibmc library is a wrapper around libmemcached, implemented in C. It is
fast, implements consistent hashing, the full memcached protocol and timeouts.
It does not provide access to the "noreply" flag, and it doesn't provide a
built-in API for serialization and deserialization. It also isn't pure Python,
so using it with libraries like gevent is out of the question.
Python-memcache
---------------
The python-memcache library implements the entire memcached text protocol, has
a single timeout for all socket calls and has a flexible approach to
serialization and deserialization. It is also written entirely in Python, so
it works well with libraries like gevent. However, it is tied to using thread
locals, doesn't implement "noreply", can't treat errors as cache misses and is
slower than both pylibmc and pymemcache. It is also tied to a specific method
for handling clusters of memcached servers.
memcache_client
---------------
The team at mixpanel put together a pure Python memcached client as well. It
has more fine grained support for socket timeouts, only connects to a single
host. However, it doesn't support most of the memcached API (just get, set,
delete and stats), doesn't support "noreply", has no serialization or
deserialization support and can't treat errors as cache misses.
External Links
==============

View File

@@ -41,13 +41,16 @@ Best Practices:
- Always set the connect_timeout and timeout arguments in the constructor to
avoid blocking your process when memcached is slow. Consider setting them
to small values like 0.05 (50ms) or less.
- Use the "noreply" flag whenever possible for a significant performance
boost. The flag is on by default, so you are using it unless you specify
otherwise.
- Use the "noreply" flag for a significant performance boot. The "noreply"
flag is enabled by default for "set", "add", "replace", "append", "prepend",
and "delete". It is disabled by default for "cas", "incr" and "decr".
- Use get_many and gets_many whenever possible, as they result in less
round trip times for fetching multiple keys.
- Use the "ignore_exc" flag to treat memcache/network errors as cache misses
on calls to the get* methods.
on calls to the get* methods. This prevents failures in memcache, or network
errors, from killing your web requests. Do not use this flag if you need to
know about errors from memcache, and make sure you have some other way to
detect memcache failures.
Not Implemented:
@@ -252,11 +255,10 @@ class Client(object):
value: str, see class docs for details.
expire: optional int, number of seconds until the item is expired
from the cache, or zero for no expiry (the default).
noreply: optional bool, False to wait for the reply (the default).
noreply: optional bool, True to not wait for the reply (the default).
Returns:
The string 'STORED' on success, or raises an Exception on error (see
class documentation).
If noreply is True, always returns None, otherwise returns 'STORED'.
"""
return self._store_cmd('set', key, expire, noreply, value)
@@ -269,11 +271,11 @@ class Client(object):
value: str, see class docs for details.
expire: optional int, number of seconds until the item is expired
from the cache, or zero for no expiry (the default).
noreply: optional bool, False to wait for the reply (the default).
noreply: optional bool, True to not wait for the reply (the default).
Returns:
The string 'STORED' if the value was stored, 'NOT_STORED' if the key
already existed, or an Exception on error (see class docs).
If noreply is True, always returns None, otherwise returns 'STORED'
if the key didn't exist already, and 'NOT_STORED' otherwise.
"""
return self._store_cmd('add', key, expire, noreply, value)
@@ -286,11 +288,12 @@ class Client(object):
value: str, see class docs for details.
expire: optional int, number of seconds until the item is expired
from the cache, or zero for no expiry (the default).
noreply: optional bool, False to wait for the reply (the default).
noreply: optional bool, True to not wait for the reply (the default).
Returns:
The string 'STORED' if the value was stored, 'NOT_STORED' if the key
didn't already exist or an Exception on error (see class docs).
If noreply is True, always returns None, otherwise returns 'STORED'
if the value was stored, and 'NOT_STORED' if the key did not already
exist.
"""
return self._store_cmd('replace', key, expire, noreply, value)
@@ -303,11 +306,10 @@ class Client(object):
value: str, see class docs for details.
expire: optional int, number of seconds until the item is expired
from the cache, or zero for no expiry (the default).
noreply: optional bool, False to wait for the reply (the default).
noreply: optional bool, True to not wait for the reply (the default).
Returns:
The string 'STORED' on success, or raises an Exception on error (see
the class docs).
If noreply is True, always returns None, otherwise returns 'STORED'.
"""
return self._store_cmd('append', key, expire, noreply, value)
@@ -323,12 +325,11 @@ class Client(object):
noreply: optional bool, False to wait for the reply (the default).
Returns:
The string 'STORED' on success, or raises an Exception on error (see
the class docs).
If noreply is True, always returns None, otherwise returns 'STORED'.
"""
return self._store_cmd('prepend', key, expire, noreply, value)
def cas(self, key, value, cas, expire=0, noreply=True):
def cas(self, key, value, cas, expire=0, noreply=False):
"""
The memcached "cas" command.
@@ -341,9 +342,9 @@ class Client(object):
noreply: optional bool, False to wait for the reply (the default).
Returns:
The string 'STORED' if the value was stored, 'EXISTS' if the key
already existed with a different cas, 'NOT_FOUND' if the key didn't
exist or raises an Exception on error (see the class docs).
If noreply is True, always returns None, otherwise returns 'STORED'
if the value was stored, 'EXISTS' if the key already existed with a
different cas value or 'NOT_FOUND' if the key didn't exist.
"""
return self._store_cmd('cas', key, expire, noreply, value, cas)
@@ -355,8 +356,7 @@ class Client(object):
key: str, see class docs for details.
Returns:
The value for the key, or None if the key wasn't found, or raises
an Exception on error (see class docs).
The value for the key, or None if the key wasn't found.
"""
return self._fetch_cmd('get', [key], False).get(key, None)
@@ -370,8 +370,7 @@ class Client(object):
Returns:
A dict in which the keys are elements of the "keys" argument list
and the values are values from the cache. The dict may contain all,
some or none of the given keys. An exception is raised on errors (see
the class docs for details).
some or none of the given keys.
"""
return self._fetch_cmd('get', keys, False)
@@ -384,7 +383,6 @@ class Client(object):
Returns:
A tuple of (key, cas), or (None, None) if the key was not found.
Raises an Exception on errors (see class docs for details).
"""
return self._fetch_cmd('gets', [key], True).get(key, (None, None))
@@ -398,8 +396,7 @@ class Client(object):
Returns:
A dict in which the keys are elements of the "keys" argument list and
the values are tuples of (value, cas) from the cache. The dict may
contain all, some or none of the given keys. An exception is raised
on errors (see the class docs for details).
contain all, some or none of the given keys.
"""
return self._fetch_cmd('gets', keys, True)
@@ -411,14 +408,13 @@ class Client(object):
key: str, see class docs for details.
Returns:
The string 'DELTED' if the key existed, and was deleted, 'NOT_FOUND'
if the string did not exist, or raises an Exception on error (see the
class docs for details).
If noreply is True, always returns None, otherwise returns 'DELETED'
if the key existed, or 'NOT_FOUND' if it did not.
"""
cmd = 'delete {}{}\r\n'.format(key, ' noreply' if noreply else '')
return self._misc_cmd(cmd, 'delete', noreply)
def incr(self, key, value, noreply=True):
def incr(self, key, value, noreply=False):
"""
The memcached "incr" command.
@@ -428,9 +424,9 @@ class Client(object):
noreply: optional bool, False to wait for the reply (the default).
Returns:
The string 'NOT_FOUND', or an integer which is the value of the key
after incrementing. Raises an Exception on errors (see the class docs
for details).
If noreply is True, always returns None, otherwise returns 'NOT_FOUND'
if the key wasn't found, or an integer which is the value of the key
after incrementing by value.
"""
cmd = "incr {} {}{}\r\n".format(
key,
@@ -443,7 +439,7 @@ class Client(object):
return result
return int(result)
def decr(self, key, value, noreply=True):
def decr(self, key, value, noreply=False):
"""
The memcached "decr" command.
@@ -453,9 +449,9 @@ class Client(object):
noreply: optional bool, False to wait for the reply (the default).
Returns:
The string 'NOT_FOUND', or an integer which is the value of the key
after decrementing. Raises an Exception on errors (see the class
docs for details).
If noreply is True, always returns None, otherwise returns 'NOT_FOUND'
if the key wasn't found, or an integer which is the value of the key
after decrementing by value.
"""
cmd = "decr {} {}{}\r\n".format(
key,
@@ -479,8 +475,7 @@ class Client(object):
noreply: optional bool, False to wait for the reply (the default).
Returns:
The string 'OK' if the value was stored or raises an Exception on
error (see the class docs).
If noreply is True, always returns None, otherwise returns 'OK'.
"""
cmd = "touch {} {}{}\r\n".format(
key,
@@ -502,8 +497,7 @@ class Client(object):
noreply: optional bool, False to wait for the response (the default).
Returns:
The string 'OK' on success, or raises an Exception on error (see the
class docs).
If noreply is True, always returns None, otherwise returns 'OK'.
"""
cmd = "flush_all {}{}\r\n".format(delay, ' noreply' if noreply else '')
return self._misc_cmd(cmd, 'flush_all', noreply)

108
pymemcache/fallback.py Normal file
View File

@@ -0,0 +1,108 @@
"""
A client for falling back to older memcached servers when performing reads.
It is sometimes necessary to deploy memcached on new servers, or with a
different configuration. In theses cases, it is undesirable to start up an
empty memcached server and point traffic to it, since the cache will be cold,
and the backing store will have a large increase in traffic.
This class attempts to solve that problem by providing an interface identical
to the Client interface, but which can fall back to older memcached servers
when reads to the primary server fail. The approach for upgrading memcached
servers or configuration then becomes:
1. Deploy a new host (or fleet) with memcached, possibly with a new
configuration.
2. From your application servers, use FallbackClient to write and read from
the new cluster, and to read from the old cluster when there is a miss in
the new cluster.
3. Wait until the new cache is warm enough to support the load.
4. Switch from FallbackClient to a regular Client library for doing all
reads and writes to the new cluster.
5. Take down the old cluster.
Best Practices:
---------------
- Make sure that the old client has "ignore_exc" set to True, so that it
treats failures like cache misses. That will allow you to take down the
old cluster before you switch away from FallbackClient.
"""
class FallbackClient(object):
def __init__(self, caches):
assert len(caches) > 0
self.caches = caches
def close(self):
"Close each of the memcached clients"
for cache in self.caches:
cache.close()
def set(self, key, value, expire=0, noreply=True):
self.caches[0].set(key, value, expire, noreply)
def add(self, key, value, expire=0, noreply=True):
self.caches[0].add(key, value, expire, noreply)
def replace(self, key, value, expire=0, noreply=True):
self.caches[0].replace(key, value, expire, noreply)
def append(self, key, value, expire=0, noreply=True):
self.caches[0].append(key, value, expire, noreply)
def prepend(self, key, value, expire=0, noreply=True):
self.caches[0].prepend(key, value, expire, noreply)
def cas(self, key, value, cas, expire=0, noreply=True):
self.caches[0].cas(key, value, cas, expire, noreply)
def get(self, key):
for cache in self.caches:
result = cache.get(key)
if result is not None:
return result
return None
def get_many(self, keys):
for cache in self.caches:
result = cache.get_many(keys)
if result:
return result
return []
def gets(self, key):
for cache in self.caches:
result = cache.gets(key)
if result is not None:
return result
return None
def gets_many(self, keys):
for cache in self.caches:
result = cache.gets_many(keys)
if result:
return result
return []
def delete(self, key, noreply=True):
self.caches[0].delete(key, noreply)
def incr(self, key, value, noreply=True):
self.caches[0].incr(key, value, noreply)
def decr(self, key, value, noreply=True):
self.caches[0].decr(key, value, noreply)
def touch(self, key, expire=0, noreply=True):
self.caches[0].touch(key, expire, noreply)
def stats(self):
# TODO: ??
pass
def flush_all(self, delay=0, noreply=True):
self.caches[0].flush_all(delay, noreply)
def quit(self):
# TODO: ??
pass