Fixing documentation, adding initial FallbackClient impl
This commit is contained in:
61
README.md
61
README.md
@@ -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
|
||||
==============
|
||||
|
||||
@@ -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
108
pymemcache/fallback.py
Normal 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
|
||||
Reference in New Issue
Block a user