============
Usage Guide
============

Overview
========

At the time of this writing, popular key/value servers include
`Memcached <http://memcached.org>`_, `Redis <http://redis.io/>`_, and `Riak <http://wiki.basho.com/>`_.
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.

Recipes
=======

Invalidating a group of related keys
-------------------------------------

This recipe presents a way to track the cache keys related to a particular region,
for the purposes of invalidating a series of keys that relate to a particular id.

Three cached functions, ``user_fn_one()``, ``user_fn_two()``, ``user_fn_three()``
each perform a different function based on a ``user_id`` integer value.  The
region applied to cache them uses a custom key generator which tracks each cache
key generated, pulling out the integer "id" and replacing with a template.

When all three functions have been called, the key generator is now aware of
these three keys:  ``user_fn_one_%d``, ``user_fn_two_%d``, and
``user_fn_three_%d``.   The ``invalidate_user_id()`` function then knows that
for a particular ``user_id``, it needs to hit all three of those keys
in order to invalidate everything having to do with that id.

::

  from dogpile.cache import make_region
  from itertools import count

  user_keys = set()
  def my_key_generator(namespace, fn):
      fname = fn.__name__
      def generate_key(*arg):
          # generate a key template:
          # "fname_%d_arg1_arg2_arg3..."
          key_template = fname + "_" + \
                              "%d" + \
                              "_".join(str(s) for s in arg[1:])

          # store key template
          user_keys.add(key_template)

          # return cache key
          user_id = arg[0]
          return key_template % user_id

      return generate_key

  def invalidate_user_id(region, user_id):
      for key in user_keys:
          region.delete(key % user_id)

  region = make_region(
      function_key_generator=my_key_generator
      ).configure(
          "dogpile.cache.memory"
      )

  counter = count()

  @region.cache_on_arguments()
  def user_fn_one(user_id):
      return "user fn one: %d, %d" % (next(counter), user_id)

  @region.cache_on_arguments()
  def user_fn_two(user_id):
      return "user fn two: %d, %d" % (next(counter), user_id)

  @region.cache_on_arguments()
  def user_fn_three(user_id):
      return "user fn three: %d, %d" % (next(counter), user_id)

  print user_fn_one(5)
  print user_fn_two(5)
  print user_fn_three(7)
  print user_fn_two(7)

  invalidate_user_id(region, 5)
  print "invalidated:"
  print user_fn_one(5)
  print user_fn_two(5)
  print user_fn_three(7)
  print user_fn_two(7)


Asynchronous Data Updates with ORM Events
-----------------------------------------

This recipe presents one technique of optimistically pushing new data
into the cache when an update is sent to a database.

Using SQLAlchemy for database querying, suppose a simple cache-decorated
function returns the results of a database query::

    @region.cache_on_arguments()
    def get_some_data(argument):
        # query database to get data
        data = Session().query(DBClass).filter(DBClass.argument == argument).all()
        return data

We would like this particular function to be re-queried when the data
has changed.  We could call ``get_some_data.invalidate(argument, hard=False)``
at the point at which the data changes, however this only
leads to the invalidation of the old value; a new value is not generated until
the next call, and also means at least one client has to block while the
new value is generated.    We could also call
``get_some_data.refresh(argument)``, which would perform the data refresh
at that moment, but then the writer is delayed by the re-query.

A third variant is to instead offload the work of refreshing for this query
into a background thread or process.   This can be acheived using
a system such as the :paramref:`.CacheRegion.async_creation_runner`.
However, an expedient approach for smaller use cases is to link cache refresh
operations to the ORM session's commit, as below::

    from sqlalchemy import event
    from sqlalchemy.orm import Session

    def cache_refresh(session, refresher, *args, **kwargs):
        """
        Refresh the functions cache data in a new thread. Starts refreshing only
        after the session was committed so all database data is available.
        """
        assert isinstance(session, Session), \
            "Need a session, not a sessionmaker or scoped_session"

        @event.listens_for(session, "after_commit")
        def do_refresh(session):
            t = Thread(target=refresher, args=args, kwargs=kwargs)
            t.daemon = True
            t.start()

Within a sequence of data persistence, ``cache_refresh`` can be called
given a particular SQLAlchemy ``Session`` and a callable to do the work::

    def add_new_data(session, argument):
        # add some data
        session.add(something_new(argument))

        # add a hook to refresh after the Session is committed.
        cache_refresh(session, get_some_data.refresh, argument)

Note that the event to refresh the data is associated with the ``Session``
being used for persistence; however, the actual refresh operation is called
with a **different** ``Session``, typically one that is local to the refresh
operation, either through a thread-local registry or via direct instantiation.


Prefixing all keys in Redis
---------------------------

If you use a redis instance as backend that contains other keys besides the ones
set by dogpile.cache, it is a good idea to uniquely prefix all dogpile.cache
keys, to avoid potential collisions with keys set by your own code.  This can
easily be done using a key mangler function::

    from dogpile.cache import make_region

    region = make_region(
      key_mangler=lambda key: "myapp:dogpile:" + key
    )


Encoding/Decoding data into another format
------------------------------------------

.. sidebar:: A Note on Data Encoding

    Under the hood, dogpile.cache wraps cached data in an instance of
    ``dogpile.cache.api.CachedValue`` and then pickles that data for storage
    along with some bookkeeping metadata. If you implement a ProxyBackend to
    encode/decode data, that transformation will happen on the pre-pickled data-
    dogpile does not store the data 'raw' and will still pass a pickled payload
    to the backend.  This behavior can negate the hopeful improvements of some
    encoding schemes.

Since dogpile is managing cached data, you may be concerned with the size of
your payloads.  A possible method of helping minimize payloads is to use a
ProxyBackend to recode the data on-the-fly or otherwise transform data as it
enters or leaves persistent storage.

In the example below, we define 2 classes to implement msgpack encoding.  Msgpack
(http://msgpack.org/) is a serialization format that works exceptionally well
with json-like data and can serialize nested dicts into a much smaller payload
than Python's own pickle.  ``_EncodedProxy`` is our base class
for building data encoders, and inherits from dogpile's own `ProxyBackend`.  You
could just use one class.  This class passes 4 of the main `key/value` functions
into a configurable decoder and encoder.  The ``MsgpackProxy`` class simply
inherits from ``_EncodedProxy`` and  implements the necessary ``value_decode``
and ``value_encode`` functions.


Encoded ProxyBackend Example::

    from dogpile.cache.proxy import ProxyBackend
    import msgpack

    class _EncodedProxy(ProxyBackend):
        """base class for building value-mangling proxies"""

        def value_decode(self, value):
            raise NotImplementedError("override me")

        def value_encode(self, value):
            raise NotImplementedError("override me")

        def set(self, k, v):
            v = self.value_encode(v)
            self.proxied.set(k, v)

        def get(self, key):
            v = self.proxied.get(key)
            return self.value_decode(v)

        def set_multi(self, mapping):
            """encode to a new dict to preserve unencoded values in-place when
               called by `get_or_create_multi`
               """
            mapping_set = {}
            for (k, v) in mapping.iteritems():
                mapping_set[k] = self.value_encode(v)
            return self.proxied.set_multi(mapping_set)

        def get_multi(self, keys):
            results = self.proxied.get_multi(keys)
            translated = []
            for record in results:
                try:
                    translated.append(self.value_decode(record))
                except Exception as e:
                    raise
            return translated


    class MsgpackProxy(_EncodedProxy):
        """custom decode/encode for value mangling"""

        def value_decode(self, v):
            if not v or v is NO_VALUE:
                return NO_VALUE
            # you probably want to specify a custom decoder via `object_hook`
            v = msgpack.unpackb(payload, encoding="utf-8")
            return CachedValue(*v)

        def value_encode(self, v):
            # you probably want to specify a custom encoder via `default`
            v = msgpack.packb(payload, use_bin_type=True)
            return v

    # extend our region configuration from above with a 'wrap'
    region = make_region().configure(
        'dogpile.cache.pylibmc',
        expiration_time = 3600,
        arguments = {
            'url': ["127.0.0.1"],
        },
        wrap = [MsgpackProxy, ]
    )
