Add caching redis backend (#466)

* Add caching redis backend

* fix mget implementation

* fix lock
This commit is contained in:
horpto 2022-01-06 23:45:11 +03:00 committed by GitHub
parent 01a8dc54d3
commit 8b34b63fd0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 3 deletions

View file

@ -1,10 +1,12 @@
from pickle import loads, dumps
from threading import RLock
from time import monotonic
from django.core.exceptions import ImproperlyConfigured
from . import Backend
from .. import settings, utils, signals, config
from pickle import loads, dumps
class RedisBackend(Backend):
@ -48,3 +50,44 @@ class RedisBackend(Backend):
signals.config_updated.send(
sender=config, key=key, old_value=old_value, new_value=value
)
class CachingRedisBackend(RedisBackend):
_sentinel = object()
_lock = RLock()
def __init__(self):
super().__init__()
self._timeout = settings.REDIS_CACHE_TIMEOUT
self._cache = {}
self._sentinel = object()
def _has_expired(self, value):
return value[0] <= monotonic()
def _cache_value(self, key, new_value):
self._cache[key] = (monotonic() + self._timeout, new_value)
def get(self, key):
value = self._cache.get(key, self._sentinel)
if value is self._sentinel or self._has_expired(value):
with self._lock:
new_value = super().get(key)
self._cache_value(key, new_value)
return new_value
return value[1]
def set(self, key, value):
with self._lock:
super().set(key, value)
self._cache_value(key, value)
def mget(self, keys):
if not keys:
return
for key in keys:
value = self.get(key)
if value is not None:
yield key, value

View file

@ -30,6 +30,8 @@ DATABASE_PREFIX = getattr(settings, 'CONSTANCE_DATABASE_PREFIX', '')
REDIS_PREFIX = getattr(settings, 'CONSTANCE_REDIS_PREFIX', 'constance:')
REDIS_CACHE_TIMEOUT = getattr(settings, 'CONSTANCE_REDIS_CACHE_TIMEOUT', 60)
REDIS_CONNECTION_CLASS = getattr(
settings,
'CONSTANCE_REDIS_CONNECTION_CLASS',

View file

@ -23,6 +23,14 @@ to add it to your project settings::
CONSTANCE_BACKEND = 'constance.backends.redisd.RedisBackend'
Default redis backend retrieves values every time. There is another redis backend with local cache.
`CachingRedisBackend` stores the value from a redis to memory at first access and checks a value ttl at next.
Configuration installation is simple::
CONSTANCE_BACKEND = 'constance.backends.redisd.CachingRedisBackend'
# optionally set a value ttl
CONSTANCE_REDIS_CACHE_TIMEOUT = 60
.. _`redis-py`: https://pypi.python.org/pypi/redis
Settings
@ -77,6 +85,12 @@ objects when storing in the Redis database. Defaults to ``pickle.DEFAULT_PROTOCO
You might want to pin this value to a specific protocol number, since ``pickle.DEFAULT_PROTOCOL``
means different things between versions of Python.
``CONSTANCE_REDIS_CACHE_TIMEOUT``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The (optional) ttl of values in seconds used by `CachingRedisBackend` for storing in a local cache.
Defaults to `60` seconds.
Database
--------

View file

@ -7,12 +7,19 @@ from tests.storage import StorageTestsMixin
class TestRedis(StorageTestsMixin, TestCase):
_BACKEND = 'constance.backends.redisd.RedisBackend'
def setUp(self):
self.old_backend = settings.BACKEND
settings.BACKEND = 'constance.backends.redisd.RedisBackend'
settings.BACKEND = self._BACKEND
super().setUp()
self.config._backend._rd.clear()
def tearDown(self):
self.config._backend._rd.clear()
settings.BACKEND = self.old_backend
class TestCachingRedis(TestRedis):
_BACKEND = 'constance.backends.redisd.CachingRedisBackend'