1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
|
============
Usage Guide
============
Overview
========
At the time of this writing, popular key/value servers include
`Memcached <http://memcached.org>`_, `Redis <http://redis.io/>`_ and many others.
While these tools all have different usage focuses, they all have in common that the storage model
is based on the retrieval of a value based on a key; as such, they are all potentially
suitable for caching, particularly Memcached which is first and foremost designed for
caching.
With a caching system in mind, dogpile.cache provides an interface to a particular Python API
targeted at that system.
A dogpile.cache configuration consists of the following components:
* A *region*, which is an instance of :class:`.CacheRegion`, and defines the configuration
details for a particular cache backend. The :class:`.CacheRegion` can be considered
the "front end" used by applications.
* A *backend*, which is an instance of :class:`.CacheBackend`, describing how values
are stored and retrieved from a backend. This interface specifies only
:meth:`~.CacheBackend.get`, :meth:`~.CacheBackend.set` and :meth:`~.CacheBackend.delete`.
The actual kind of :class:`.CacheBackend` in use for a particular :class:`.CacheRegion`
is determined by the underlying Python API being used to talk to the cache, such
as Pylibmc. The :class:`.CacheBackend` is instantiated behind the scenes and
not directly accessed by applications under normal circumstances.
* Value generation functions. These are user-defined functions that generate
new values to be placed in the cache. While dogpile.cache offers the usual
"set" approach of placing data into the cache, the usual mode of usage is to only instruct
it to "get" a value, passing it a *creation function* which will be used to
generate a new value if and only if one is needed. This "get-or-create" pattern
is the entire key to the "Dogpile" system, which coordinates a single value creation
operation among many concurrent get operations for a particular key, eliminating
the issue of an expired value being redundantly re-generated by many workers simultaneously.
Rudimentary Usage
=================
dogpile.cache includes a Pylibmc backend. A basic configuration looks like::
from dogpile.cache import make_region
region = make_region().configure(
'dogpile.cache.pylibmc',
expiration_time = 3600,
arguments = {
'url': ["127.0.0.1"],
}
)
@region.cache_on_arguments()
def load_user_info(user_id):
return some_database.lookup_user_by_id(user_id)
.. sidebar:: pylibmc
In this section, we're illustrating Memcached usage
using the `pylibmc <http://pypi.python.org/pypi/pylibmc>`_ backend, which is a high performing
Python library for Memcached. It can be compared to the `python-memcached <http://pypi.python.org/pypi/python-memcached>`_
client, which is also an excellent product. Pylibmc is written against Memcached's native API
so is markedly faster, though might be considered to have rougher edges. The API is actually a bit
more verbose to allow for correct multithreaded usage.
Above, we create a :class:`.CacheRegion` using the :func:`.make_region` function, then
apply the backend configuration via the :meth:`.CacheRegion.configure` method, which returns the
region. The name of the backend is the only argument required by :meth:`.CacheRegion.configure`
itself, in this case ``dogpile.cache.pylibmc``. However, in this specific case, the ``pylibmc``
backend also requires that the URL of the memcached server be passed within the ``arguments`` dictionary.
The configuration is separated into two sections. Upon construction via :func:`.make_region`,
the :class:`.CacheRegion` object is available, typically at module
import time, for usage in decorating functions. Additional configuration details passed to
:meth:`.CacheRegion.configure` are typically loaded from a configuration file and therefore
not necessarily available until runtime, hence the two-step configurational process.
Key arguments passed to :meth:`.CacheRegion.configure` include *expiration_time*, which is the expiration
time passed to the Dogpile lock, and *arguments*, which are arguments used directly
by the backend - in this case we are using arguments that are passed directly
to the pylibmc module.
Region Configuration
====================
The :func:`.make_region` function currently calls the :class:`.CacheRegion` constructor directly.
.. autoclass:: dogpile.cache.region.CacheRegion
:noindex:
One you have a :class:`.CacheRegion`, the :meth:`.CacheRegion.cache_on_arguments` method can
be used to decorate functions, but the cache itself can't be used until
:meth:`.CacheRegion.configure` is called. The interface for that method is as follows:
.. automethod:: dogpile.cache.region.CacheRegion.configure
:noindex:
The :class:`.CacheRegion` can also be configured from a dictionary, using the :meth:`.CacheRegion.configure_from_config`
method:
.. automethod:: dogpile.cache.region.CacheRegion.configure_from_config
:noindex:
Using a Region
==============
The :class:`.CacheRegion` object is our front-end interface to a cache. It includes
the following methods:
.. automethod:: dogpile.cache.region.CacheRegion.get
:noindex:
.. automethod:: dogpile.cache.region.CacheRegion.get_or_create
:noindex:
.. automethod:: dogpile.cache.region.CacheRegion.set
:noindex:
.. automethod:: dogpile.cache.region.CacheRegion.delete
:noindex:
.. automethod:: dogpile.cache.region.CacheRegion.cache_on_arguments
:noindex:
.. _creating_backends:
Creating Backends
=================
Backends are located using the setuptools entrypoint system. To make life easier
for writers of ad-hoc backends, a helper function is included which registers any
backend in the same way as if it were part of the existing sys.path.
For example, to create a backend called ``DictionaryBackend``, we subclass
:class:`.CacheBackend`::
from dogpile.cache.api import CacheBackend, NO_VALUE
class DictionaryBackend(CacheBackend):
def __init__(self, arguments):
self.cache = {}
def get(self, key):
return self.cache.get(key, NO_VALUE)
def set(self, key, value):
self.cache[key] = value
def delete(self, key):
self.cache.pop(key)
Then make sure the class is available underneath the entrypoint
``dogpile.cache``. If we did this in a ``setup.py`` file, it would be
in ``setup()`` as::
entry_points="""
[dogpile.cache]
dictionary = mypackage.mybackend:DictionaryBackend
"""
Alternatively, if we want to register the plugin in the same process
space without bothering to install anything, we can use ``register_backend``::
from dogpile.cache import register_backend
register_backend("dictionary", "mypackage.mybackend", "DictionaryBackend")
Our new backend would be usable in a region like this::
from dogpile.cache import make_region
region = make_region("myregion")
region.configure("dictionary")
data = region.set("somekey", "somevalue")
The values we receive for the backend here are instances of
``CachedValue``. This is a tuple subclass of length two, of the form::
(payload, metadata)
Where "payload" is the thing being cached, and "metadata" is information
we store in the cache - a dictionary which currently has just the "creation time"
and a "version identifier" as key/values. If the cache backend requires serialization,
pickle or similar can be used on the tuple - the "metadata" portion will always
be a small and easily serializable Python structure.
.. _changing_backend_behavior:
Changing Backend Behavior
=========================
The :class:`.ProxyBackend` is a decorator class provided to easily augment existing
backend behavior without having to extend the original class. Using a decorator
class is also adventageous as it allows us to share the altered behavior between
different backends.
Proxies are added to the :class:`.CacheRegion` object using the :meth:`.CacheRegion.configure`
method. Only the overridden methods need to be specified and the real backend can
be accessed with the ``self.proxied`` object from inside the :class:`.ProxyBackend`.
For example, a simple class to log all calls to ``.set()`` would look like this::
from dogpile.cache.proxy import ProxyBackend
import logging
log = logging.getLogger(__name__)
class LoggingProxy(ProxyBackend):
def set(self, key, value):
log.debug('Setting Cache Key: %s' % key)
self.proxied.set(key, value)
:class:`.ProxyBackend` can be be configured to optionally take arguments (as long as the
:meth:`.ProxyBackend.__init__` method is called properly, either directly
or via ``super()``. In the example
below, the ``RetryDeleteProxy`` class accepts a ``retry_count`` parameter
on initialization. In the event of an exception on delete(), it will retry
this many times before returning::
from dogpile.cache.proxy import ProxyBackend
class RetryDeleteProxy(ProxyBackend):
def __init__(self, retry_count=5):
super(RetryDeleteProxy, self).__init__()
self.retry_count = retry_count
def delete(self, key):
retries = self.retry_count
while retries > 0:
retries -= 1
try:
self.proxied.delete(key)
return
except:
pass
The ``wrap`` parameter of the :meth:`.CacheRegion.configure` accepts a list
which can contain any combination of instantiated proxy objects
as well as uninstantiated proxy classes.
Putting the two examples above together would look like this::
from dogpile.cache import make_region
retry_proxy = RetryDeleteProxy(5)
region = make_region().configure(
'dogpile.cache.pylibmc',
expiration_time = 3600,
arguments = {
'url':["127.0.0.1"],
},
wrap = [ LoggingProxy, retry_proxy ]
)
In the above example, the ``LoggingProxy`` object would be instantated by the
:class:`.CacheRegion` and applied to wrap requests on behalf of
the ``retry_proxy`` instance; that proxy in turn wraps
requests on behalf of the original dogpile.cache.pylibmc backend.
.. versionadded:: 0.4.4 Added support for the :class:`.ProxyBackend` class.
Configuring Logging
====================
.. versionadded:: 0.9.0
:class:`.CacheRegion` includes logging facilities that will emit debug log
messages when key cache events occur, including when keys are regenerated as
well as when hard invalidations occur. Using the `Python logging
<https://docs.python.org/3/library/logging.html>`_ module, set the log level to
``dogpile.cache`` to ``logging.DEBUG``::
logging.basicConfig()
logging.getLogger("dogpile.cache").setLevel(logging.DEBUG)
Debug logging will indicate time spent regenerating keys as well as when
keys are missing::
DEBUG:dogpile.cache.region:No value present for key: '__main__:load_user_info|2'
DEBUG:dogpile.cache.region:No value present for key: '__main__:load_user_info|1'
DEBUG:dogpile.cache.region:Cache value generated in 0.501 seconds for keys: ['__main__:load_user_info|2', '__main__:load_user_info|3', '__main__:load_user_info|4', '__main__:load_user_info|5']
DEBUG:dogpile.cache.region:Hard invalidation detected for key: '__main__:load_user_info|3'
DEBUG:dogpile.cache.region:Hard invalidation detected for key: '__main__:load_user_info|2'
|