Вопрос

Я хотел бы реализовать декоратор, который обеспечивает кэширование каждого запроса на любой метод, а не просто взгляды. Вот пример использования корпуса.

У меня есть пользовательский тег, который определяет, если запись в длинном списке записей является «любимым». Чтобы проверить, является ли элемент любимым, вы должны запросить базу данных. В идеале вы выполнили один запрос, чтобы получить все избранное, а затем просто проверить этот кэшированный список против каждой записи.

Одно решение состоит в том, чтобы получить все избранное в поле зрения, а затем пройти, чтобы установить в шаблон, а затем в каждый вызов тега.

Альтернативно, сам тег может выполнить саму запрос, но только первый раз, когда он называется. Затем результаты могут быть кэшированы для последующих вызовов. Увеличивается, что вы можете использовать этот тег из любого шаблона, на любом виде, не предупреждая обзор.

В существующем механизме кэширования вы можете просто кэшировать результат для 50 мс и предположим, что коррелирует бы к текущему запросу. Я хочу сделать эту корреляцию надежным.

Вот пример тега, который у меня сейчас есть.

@register.filter()
def is_favorite(record, request):

    if "get_favorites" in request.POST:
        favorites = request.POST["get_favorites"]
    else:

        favorites = get_favorites(request.user)

        post = request.POST.copy()
        post["get_favorites"] = favorites
        request.POST = post

    return record in favorites

Есть ли способ получить текущий объект запроса из Django, с переходом его? Из тега я мог бы просто пройти по запросу, что всегда будет существовать. Но я хотел бы использовать этот декоратор из других функций.

Есть ли существующая реализация кэша за запрос?

Это было полезно?

Решение

Используя пользовательское промежуточное программное обеспечение, вы можете получить экземпляр кеша Django, гарантированного для каждого запроса.

Это то, что я использовал в проекте:

from threading import currentThread
from django.core.cache.backends.locmem import LocMemCache

_request_cache = {}
_installed_middleware = False

def get_request_cache():
    assert _installed_middleware, 'RequestCacheMiddleware not loaded'
    return _request_cache[currentThread()]

# LocMemCache is a threadsafe local memory cache
class RequestCache(LocMemCache):
    def __init__(self):
        name = 'locmemcache@%i' % hash(currentThread())
        params = dict()
        super(RequestCache, self).__init__(name, params)

class RequestCacheMiddleware(object):
    def __init__(self):
        global _installed_middleware
        _installed_middleware = True

    def process_request(self, request):
        cache = _request_cache.get(currentThread()) or RequestCache()
        _request_cache[currentThread()] = cache

        cache.clear()

Чтобы использовать промежуточное программное обеспечение, регистрируйте его в настройках .py, например:

MIDDLEWARE_CLASSES = (
    ...
    'myapp.request_cache.RequestCacheMiddleware'
)

Затем вы можете использовать кэш следующим образом:

from myapp.request_cache import get_request_cache

cache = get_request_cache()

Обратитесь к Django Lowlow Cache Cache API DOC для получения дополнительной информации:

Django низкоуровневый кеш-API кэш

Должно быть легко изменить декоратор Memoiese для использования кэша запроса. Посмотрите на библиотеку Python Decorator для хорошего примера декоратора воспоминания:

Библиотека декоратора Python

Другие советы

Я придумал взлом для кэширования вещей прямо в объект запроса (вместо того, чтобы использовать стандартный кэш, который будет привязан к memcached, файлу, базу данных и т. Д.)

# get the request object's dictionary (rather one of its methods' dictionary)
mycache = request.get_host.__dict__

# check whether we already have our value cached and return it
if mycache.get( 'c_category', False ):
    return mycache['c_category']
else:
    # get some object from the database (a category object in this case)
    c = Category.objects.get( id = cid )

    # cache the database object into a new key in the request object
    mycache['c_category'] = c

    return c

Итак, в основном я просто храняю кэшированную ценность (объект категории в этом случае) в соответствии с новой ключевой «C_CATEGORY» в словаре запроса. Или быть более точным, потому что мы не можем просто создать ключ на объекте запроса, я добавляю ключ к одному из методов объекта запроса - get_host ().

Георгий.

Годы спустя, супер хак для кэширования выбора утверждений в одном запросе Django. Вам нужно выполнить patch() Метод от начала в вашем запросе, как в куске промежуточного программного обеспечения.

from threading import local
import itertools
from django.db.models.sql.constants import MULTI
from django.db.models.sql.compiler import SQLCompiler
from django.db.models.sql.datastructures import EmptyResultSet
from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE


_thread_locals = local()


def get_sql(compiler):
    ''' get a tuple of the SQL query and the arguments '''
    try:
        return compiler.as_sql()
    except EmptyResultSet:
        pass
    return ('', [])


def execute_sql_cache(self, result_type=MULTI):

    if hasattr(_thread_locals, 'query_cache'):

        sql = get_sql(self)  # ('SELECT * FROM ...', (50)) <= sql string, args tuple
        if sql[0][:6].upper() == 'SELECT':

            # uses the tuple of sql + args as the cache key
            if sql in _thread_locals.query_cache:
                return _thread_locals.query_cache[sql]

            result = self._execute_sql(result_type)
            if hasattr(result, 'next'):

                # only cache if this is not a full first page of a chunked set
                peek = result.next()
                result = list(itertools.chain([peek], result))

                if len(peek) == GET_ITERATOR_CHUNK_SIZE:
                    return result

            _thread_locals.query_cache[sql] = result

            return result

        else:
            # the database has been updated; throw away the cache
            _thread_locals.query_cache = {}

    return self._execute_sql(result_type)


def patch():
    ''' patch the django query runner to use our own method to execute sql '''
    _thread_locals.query_cache = {}
    if not hasattr(SQLCompiler, '_execute_sql'):
        SQLCompiler._execute_sql = SQLCompiler.execute_sql
        SQLCompiler.execute_sql = execute_sql_cache

Метод PATCH () заменяет метод Django Internal Execute_SQL с подставкой SEVELED_SQL_CACHE. Этот метод смотрит на SQL, который должен быть запущен, и если это выберите оператор, он сначала проверяет локальный кеш поток. Только если он не найден в кэше, это продолжается выполнить SQL. На любом другом типе SQL-заявления оно дует кэш. Есть какая-то логика, чтобы не кэшировать большие наборы результатов, означающих что-либо более 100 записей. Это состоит в том, чтобы сохранить оценку ленивого запроса Django.

Редактировать: возможное решение, которое я придумал, был скомпилирован в пакет Pypi: https://pypi.org/project/django-request-cache/

Основной проблемой, которую здесь не решает никакого другого решения, является тот факт, что Locmemcache утечки памяти, когда вы создаете и уничтожаете несколько из них из-за срока службы одного процесса. django.core.cache.backends.locmem Определяет несколько глобальных словарей, которые содержат ссылки на данные кэша экземпляра LostalMemcache, а эти словари никогда не опорожняются.

Следующий код решает эту проблему. Он начался как комбинация ответа @ Href_ и логика очистительной, используемой кодом, связанным в комментариях @ @ Squarelogic.hayden, который я затем уточнил дальше.

from uuid import uuid4
from threading import current_thread

from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock


# Global in-memory store of cache data. Keyed by name, to provides multiple
# named local memory caches.
_caches = {}
_expire_info = {}
_locks = {}


class RequestCache(LocMemCache):
    """
    RequestCache is a customized LocMemCache with a destructor, ensuring that creating
    and destroying RequestCache objects over and over doesn't leak memory.
    """

    def __init__(self):
        # We explicitly do not call super() here, because while we want
        # BaseCache.__init__() to run, we *don't* want LocMemCache.__init__() to run.
        BaseCache.__init__(self, {})

        # Use a name that is guaranteed to be unique for each RequestCache instance.
        # This ensures that it will always be safe to call del _caches[self.name] in
        # the destructor, even when multiple threads are doing so at the same time.
        self.name = uuid4()
        self._cache = _caches.setdefault(self.name, {})
        self._expire_info = _expire_info.setdefault(self.name, {})
        self._lock = _locks.setdefault(self.name, RWLock())

    def __del__(self):
        del _caches[self.name]
        del _expire_info[self.name]
        del _locks[self.name]


class RequestCacheMiddleware(object):
    """
    Creates a cache instance that persists only for the duration of the current request.
    """

    _request_caches = {}

    def process_request(self, request):
        # The RequestCache object is keyed on the current thread because each request is
        # processed on a single thread, allowing us to retrieve the correct RequestCache
        # object in the other functions.
        self._request_caches[current_thread()] = RequestCache()

    def process_response(self, request, response):
        self.delete_cache()
        return response

    def process_exception(self, request, exception):
        self.delete_cache()

    @classmethod
    def get_cache(cls):
        """
        Retrieve the current request's cache.

        Returns None if RequestCacheMiddleware is not currently installed via 
        MIDDLEWARE_CLASSES, or if there is no active request.
        """
        return cls._request_caches.get(current_thread())

    @classmethod
    def clear_cache(cls):
        """
        Clear the current request's cache.
        """
        cache = cls.get_cache()
        if cache:
            cache.clear()

    @classmethod
    def delete_cache(cls):
        """
        Delete the current request's cache object to avoid leaking memory.
        """
        cache = cls._request_caches.pop(current_thread(), None)
        del cache

Редактировать 2016-06-15: Я обнаружил значительно прощественное решение этой проблемы, и вроде рискованного выплата, чтобы не осознавать, насколько легко это было с самого начала.

from django.core.cache.backends.base import BaseCache
from django.core.cache.backends.locmem import LocMemCache
from django.utils.synch import RWLock


class RequestCache(LocMemCache):
    """
    RequestCache is a customized LocMemCache which stores its data cache as an instance attribute, rather than
    a global. It's designed to live only as long as the request object that RequestCacheMiddleware attaches it to.
    """

    def __init__(self):
        # We explicitly do not call super() here, because while we want BaseCache.__init__() to run, we *don't*
        # want LocMemCache.__init__() to run, because that would store our caches in its globals.
        BaseCache.__init__(self, {})

        self._cache = {}
        self._expire_info = {}
        self._lock = RWLock()

class RequestCacheMiddleware(object):
    """
    Creates a fresh cache instance as request.cache. The cache instance lives only as long as request does.
    """

    def process_request(self, request):
        request.cache = RequestCache()

С этим вы можете использовать request.cache в качестве экземпляра кэша, который живет только до тех пор, пока request делает, и будет полностью очищен сборщиком мусора, когда запрос сделан.

Если вам нужен доступ к request Объект из контекста, где он обычно не доступен, вы можете использовать одну из различных реализаций так называемого «Milverware Global запроса», которые можно найти в Интернете.

Этот использует Python Dict в качестве кеша (не кэш Django), а не мертвый простой и легкий.

  • Всякий раз, когда нить разрушается, это кеш будет слишком автоматически.
  • Не требует промежуточного программного обеспечения, а содержание не замаривается и поминет каждый доступ, что быстрее.
  • Проверено и работает с обезьянами Gevent.

То же самое можно будет реализовано с помощью ThreadLocal Storage. Я не знаю о каких-либо недостатках такого подхода, не стесняйтесь добавлять их в комментарии.

from threading import currentThread
import weakref

_request_cache = weakref.WeakKeyDictionary()

def get_request_cache():
    return _request_cache.setdefault(currentThread(), {})

Вы всегда можете сделать кэширование вручную.

    ...
    if "get_favorites" in request.POST:
        favorites = request.POST["get_favorites"]
    else:
        from django.core.cache import cache

        favorites = cache.get(request.user.username)
        if not favorites:
            favorites = get_favorites(request.user)
            cache.set(request.user.username, favorites, seconds)
    ...

Отвечать Дано @href_ отлично.

На всякий случай, если вы хотите что-то более короткое, которое также может потенциально сделать трюк:

from django.utils.lru_cache import lru_cache

def cached_call(func, *args, **kwargs):
    """Very basic temporary cache, will cache results
    for average of 1.5 sec and no more then 3 sec"""
    return _cached_call(int(time.time() / 3), func, *args, **kwargs)


@lru_cache(maxsize=100)
def _cached_call(time, func, *args, **kwargs):
    return func(*args, **kwargs)

Тогда получите избранное, называя это так:

favourites = cached_call(get_favourites, request.user)

Этот метод использует LRU кэш И сочетая его со временем времени, мы уверены, что кэш не удерживает ничего дольше, чем несколько секунд. Если вам нужно назвать дорогостоящую функцию несколько раз в течение короткого периода времени, это решает проблему.

Это не идеальный способ недействительным кэш, потому что иногда он будет пропустить в последние данные: int(..2.99.. / 3) с последующим int(..3.00..) / 3). Отказ Несмотря на этот недостаток, он все еще может быть очень эффективен в большинстве ударов.

Также как бонус вы можете использовать его вне циклов запроса / ответа, например, задачи сельдерей или задания управления управления.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top