cacheme

A memoized/cache decorator for Python using redis.


Keywords
cache, memoization, python, redis
License
BSD-3-Clause
Install
pip install cacheme==0.0.9

Documentation

Build Status Build Status

Cacheme(WIP)

A memoized/cache decorator for Python using redis.

Features

  • Generate key based on function args/kwargs

  • Avoid thundering herd using stale data

  • Skip cache based on function args/kwargs

  • Get/invalid all keys generated by function(using tag)

  • Hit/miss function support

  • Timeout(ttl) support

Getting started

pip install cacheme

Find a good place to init cacheme globally, for example a foobar_cache.py file

import redis
from cacheme import cacheme

r = redis.Redis()

settings = {
    'ENABLE_CACHE': True,
    'REDIS_CACHE_ALIAS': 'cacheme',  # your CACHES alias name in settings, optional, 'default' as default
    'REDIS_CACHE_PREFIX': 'MYCACHE:',  # cacheme key prefix, optional, 'CM:' as default
    'THUNDERING_HERD_RETRY_COUNT': 5,  # thundering herd retry count, if key missing, default 5
    'THUNDERING_HERD_RETRY_TIME': 20  # thundering herd wait time(millisecond) between each retry, default 20
}

cacheme.set_connection(r)
cacheme.update_settings(settings)

Then in your project, when you need cacheme, just:

from foobar_cache import cacheme

Example

serializers.py

from foobar_cache import cacheme


class BookSerializer(object):

    @cacheme(
        key=lambda c: c.obj.cache_key + ">" + "owner",
        invalid_keys=lambda c: [c.obj.owner.cache_key]
    )
    def get_owner(self, obj):
        return BookOwnerSerializer(obj.owner).data
	

We have a book, id is 100, and a user, id is 200. And we want to cache book owner data in serializer. So the cache key will be Book:100>owner, "Book:100" as key, and "owner" as field in redis.

Invalid key will be User:200:invalid, the ":invalid" suffix is auto added. And the redis data type of this key is set. The Book:100>owner key will be stored under this invalid key.

Introduction

For complicated page or API, you may need to fetch data from a variety of sources such as MySQL databases, HDFS installations, some machine learning engines or your backend services. This heterogeneity requires a flexible caching strategy able to store data from disparate sources. And cacheme or memoize can help you.

How to use

- Cacheme Decorator

Cacheme need following params when init the decorator.

  • key: Callable, required. The func to generate the cache key, will call this func when the key is needed.

  • invalid_keys: Callable or None, default None. an invalid key that will store this key, use redis set, and the key func before will be stored in this invalid key.

  • hit: callback when cache hit, need 3 arguments (key, result, container)

  • miss: callback when cache miss, need 2 arguments (key, container)

  • tag: string, default func name. using tag to get cache instance, then get all keys under that tag.

    
    instance = cacheme.tags[tag]
    
    # get all keys
    keys = instance.keys
    
    # invalid all keys
    instance.invalid_all()
    
  • skip: boolean or callable, default False. If value or callable value return true, will skip cache. For example, you can cache result if request param has user, but return None directly, if no user.

  • timeout: set ttl for this key, default None

  • invalid_sources: something cause invalidation (for example Django/Flask signals). To use this, You need to override connect(self, source) in your cache class.

- Invalid

You can create invalidation using following method:

cacheme.create_invalidation(key=None, invalid_key=None, pattern=None)

create_invalidation support 3 types of invalidation:

  • key: invalid one key

  • invalid_key: same as decorator, will invalid all keys saved in this key

  • pattern: invalid a redis pattern, for example test*

Default for all 3 types are None, and you can use them together

Tips:

  • key and invalid_keys callable: the first argument in the callable is the container, this container contains the args and kwargs for you function. For example, if your function is def func(a, b, **kwargs), then you can access a and b in your callable by container.a, container.b, also container.kwargs.

  • For invalid_keys callable, you can aslo get your function result through container.cacheme_result, so you can invalid based on this result.

  • if code is changed, developer should check if cache should invalid or not, for example you add some fields to json, then cache for that json should be invalid, there is no signal for this, so do it manually

  • How cacheme avoid thundering herds: if there is stale data, use stale data until new data fill in, if there is no stale data, just wait a short time and retry.

  • For keys with timeout set, because cacheme store k/v using hash, we also store timeout in another redis sorted set

  • There is another thing you can do to avoid thundering herds, if you use cacheme in a class, for example a Serializer, and cache many methods in this class, and, order of these methods does not matter. Then you can make the order of call to theses methods randomly. For example, if your class has 10 cached methods, and 100 clients call this method same time, then some clients will call method1 first, some will call method2 first..., so they can run in parallel.