Add the concept of a data model cache, for caching of Namedtuple objects from the data model

Will be used to cache blobs, thus removing the need to hit the database in most blob requests
This commit is contained in:
Joseph Schorr 2017-12-14 13:36:51 -05:00
parent 51e67ab7f5
commit 3c72e9878d
3 changed files with 72 additions and 0 deletions

48
data/cache/__init__.py vendored Normal file
View file

@ -0,0 +1,48 @@
from datetime import datetime
from abc import ABCMeta, abstractmethod
from six import add_metaclass
from util.expiresdict import ExpiresDict
from util.timedeltastring import convert_to_timedelta
def is_not_none(value):
return value is not None
@add_metaclass(ABCMeta)
class DataModelCache(object):
""" Defines an interface for cache storing and returning tuple data model objects. """
@abstractmethod
def retrieve(self, cache_key, loader, should_cache=is_not_none):
""" Checks the cache for the specified cache key and returns the value found (if any). If none
found, the loader is called to get a result and populate the cache.
"""
pass
class NoopDataModelCache(DataModelCache):
""" Implementation of the data model cache which does nothing. """
def retrieve(self, cache_key, loader, should_cache=is_not_none):
return loader()
class InMemoryDataModelCache(DataModelCache):
""" Implementation of the data model cache backed by an in-memory dictionary. """
def __init__(self):
self.cache = ExpiresDict(rebuilder=lambda: {})
def retrieve(self, cache_key, loader, should_cache=is_not_none):
not_found = [None]
result = self.cache.get(cache_key.key, default_value=not_found)
if result != not_found:
return result
result = loader()
if should_cache(result):
expires = convert_to_timedelta(cache_key.expiration) + datetime.now()
self.cache.set(cache_key.key, result, expires=expires)
return result

8
data/cache/cache_key.py vendored Normal file
View file

@ -0,0 +1,8 @@
from collections import namedtuple
class CacheKey(namedtuple('CacheKey', ['key', 'expiration'])):
""" Defines a key into the data model cache. """
pass
def for_repository_blob(namespace_name, repo_name, digest):
return CacheKey('repository_blob:%s:%s:%s' % (namespace_name, repo_name, digest), '60s')

16
data/cache/test/test_cache.py vendored Normal file
View file

@ -0,0 +1,16 @@
import pytest
from data.cache import InMemoryDataModelCache, NoopDataModelCache
from data.cache.cache_key import CacheKey
@pytest.mark.parametrize('cache_type', [
(NoopDataModelCache),
(InMemoryDataModelCache),
])
def test_caching(cache_type):
key = CacheKey('foo', '60m')
cache = cache_type()
# Perform two retrievals, and make sure both return.
assert cache.retrieve(key, lambda: 1234) == 1234
assert cache.retrieve(key, lambda: 1234) == 1234