File: usage.rst

package info (click to toggle)
python-dogpile.cache 1.3.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 772 kB
  • sloc: python: 5,462; makefile: 155; sh: 104
file content (293 lines) | stat: -rw-r--r-- 11,625 bytes parent folder | download | duplicates (3)
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'