EVILEG-CORE. Кэширование свойств объектов моделей с помощью model_cached_property

ESNF-C, Django, model_cached_property, EVILEG

Для ускорения работы сайта помимо оптимизации запросов к базе данных можно использовать и кэширование.

Django позволяет выполнять кэширование:

  • отдельных view , как Class Based View , так и обычных функций view
  • целых шаблонов или их частей
  • целых QuerySet
  • а также свойств объектов моделей с помощью cached_property

Меня интересовала возможность кэширования отдельных свойств объектов моделей для тяжёлых вычислений или тяжёлых запросов к базе данных.
Таким фукционалом обладает декоратор cached_property , но недостаток для меня заключался в том, что кэширование происходило лишь на период жизни объекта.
Тогда как мне требуется кэширование на больший период времени, чем существование объекта при запросе страницы. А также мне требовалось кэширование свойств в зависимости от входных аргументов. Данный декоратор на сайте кэширует количество лайков и дислайков, а также информацию о том, лайкнул ли текущий пользователь тот или иной объект контента.

Таким образом был написан декоратор model_cached_property

model_cached_property

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

Установка EVILEG-CORE

Устанавливаем EVILEG-CORE.

pip install evileg-core

Также evileg_core подтянет все необходимые для данного пакета зависимости. В том числе библиотеку django-redis , которая используется для работы с redis .

Если вы ещё не используете у себя redis , то потребуется его установить.

sudo apt install redis-server

settings.py

Добавляем evileg_core в установленные приложения

INSTALLED_APPS = [
    ...
    'evileg_core',
]

Настраиваем бэкенд кэширования

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

Использование model_cached_property

Обычное кэширование

from evileg_core.cache.decorators import model_cached_property

class Article(models.Model):

    @model_cached_property
    def comments_count(self):
        return self.comments.count()

Данный декоратор будет кэшировать количество комментариев к статье на 60 секунд. При следующем запросе страницы статьи запрос будет выполнен сначала к кэшу для конкретного объекта статьи, и только если длительность кэша истекла, то запрос будет снова выполнен к базе данных.

Установка длительности кэширования

Если же вы хотите задать определённое время кэширования, то можете использовать аргумент timeout у декоратора.

class Article(models.Model):

    @model_cached_property(timeout=3000)
    def comments_count(self):
        return self.comments.count()

Глобальная установка длительности кэширования

Также можно задать глобальное время кэширования для всех декораторов в файле settings.py в секундах

MODEL_CACHED_PROPERTY_TIMEOUT = 300000

Использование свойств с аргументами

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

Это является как преимуществом данного декоратора, так и его недостатком. Дело в том, чтобы кэширование было корректным, необходимо, чтобы входные аргументы были уникальными. Например, если входные аргументы являются временными неуникальными объектами, как AnonymousUser , то кэширование не будет работать.

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

class Article(models.Model):

    @model_cached_property
    def __user_in_bookmarks(self, user):
        return self.bookmarks.filter(user=user).exists()

    def user_in_bookmarks(self, user):
        return self.__user_in_bookmarks(user) if user.is_authenticated else False

Обратите внимание, что здесь имеется проверка на user.is_authenticated , поскольку кэширование неаутентифицированного пользователя не будет работать корректно, однако будет работать корректно для аутентифицированного пользователя, поскольку аутентифицированный пользователь является уникальным объектом.

Инвалидация кэша

В том случае, если кэш стал неактуальным раньше времени истечения его жизни, то вы можете воспользоваться функцией invalidate_model_cached_property

from evileg_core.cache.utils import invalidate_model_cached_property

class Article(models.Model):

    def invalidate_cache(self):
        invalidate_model_cached_property(self, self.comments_count)

Для корректной инвалидации кэша необходимо передать объект, кэш которого необходимо очистить, а также метод этого объекта.

Это может выглядеть также и таким образом

article = get_object_or_404(Article, pk=12)
invalidate_model_cached_property(article, article.comments_count)

Заключение

Таким образом model_cached_property может использоваться для

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

При этом имеются ограничения

  • данный декоратор может использоваться только в моделях, то есть классов, наследованных от models.Model
  • кэширование свойств моделей в зависимости от аргументов должно выполняться только для уникальных входных аргументов, иначе кэширование не будет корректным
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
- блог компании
Поддержать автора Donate
МК

а functools.lru_cache в данном случае не поможет?

Добрый день.

Думаю, что не совсем. Технически здесь решается задача кэширования не последних вызовов функции, как в lru_cache, а кэширование свойств для ряда объектов модели данных. К тому же cache_clear() будет полностью удалять весь кэш, тогда как у меня предусмотрен более специфический функционал для инвалидации части ключей, которые перестали быть актуальны.

Давайте поясню на примере.

На сайте есть модель данных ForumTopic , у которой есть лайки и дислайки, а также они подсвечиваются, если пользователь выбрал лайк или дислайк. Вся эта информация у меня кэшируется, поскольку для лайков и дислайков используются GenericForeignKey и запросы к базе данных посылаются отдельно, что получается несколько накладно для страниц со списком тем на форуме. Поэтому я предпочитаю кэшировать эти данные и при срабатывание некоторых событий делать инвалидацию кэша.

Так вот, если использовать lru_cache, то он будет кэшировать лишь n-последних вызовов метода, то есть придётся отслеживать количество контента и вручную поправлять количество последних кэширований, если решать эту задачу в лоб. А в случае очистки кэша, при вызове страниц будут заново кэшироваться все свойства для всех объектов ForumTopic. Получается ситуация, если пользователь лайкнул один единственный пост, то кэш будет очищаться для всех объектов, чего я не хочу.

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

Когда вы вызываете функцию invalidate_model_cached_property, то кэш очищается только для конкретного объекта базы данных.

Таким образом, когда пользователь лайкнет один какой-то объект ForumTopic , то кэш будет очищен только для этого ForumTopic . И при следующем запросе список объектов ForumTopic будет выполняться запрос о лайках и дислайках только для одного единственного объекта. Плюс я не беспокоюсь о том, что мне нужно думать, какой максимальный размер кэша нужно делать.

Конечно, вопрос расхода памяти на кэширование является довольно важным и если появится бутылочное горлышко, то я буду его решать. Но на данный момент я поставил у себя длительность кэширования подобных активностей, как лайки, на целый месяц и на данный момент ещё не заметил проблем.

Но если я что-то не учёл и у вас есть, что дополнить, то с удовольствием выслушаю вашу точку зрения, возможно, что это поможет доработать функциональность model_cached_property

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Donate

Здравствуйте, уважаемые пользователи EVILEG !!!

Если сайт вам помог, то поддержите разработку сайта финансово, пожалуйста.

Вы можете сделать это следующими способами:

Спасибо, Евгений Легоцкой

p
17 февраля 2020 г. 14:41
pstMem

C++ - Тест 003. Условия и циклы

  • Результат:85баллов,
  • Очки рейтинга6
z
17 февраля 2020 г. 6:02
zet

C++ - Тест 006. Перечисления

  • Результат:80баллов,
  • Очки рейтинга4
z
17 февраля 2020 г. 5:49
zet

C++ - Тест 001. Первая программа и типы данных

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
17 февраля 2020 г. 3:22
Евгений Легоцкой

Добрый день. Это кастомный тег, помещается в файл, который находится в каталоге templatetags myapp/ templatetags/ myapp.py
B
16 февраля 2020 г. 13:36
BahaMeirman

Добрый вечер! Монжно по подробней о теге get_companion? ссылка не работает.
16 февраля 2020 г. 9:35
Евгений Легоцкой

Добрый день. На GitHub исходники, можете посмотреть в официальном репозитории
B
16 февраля 2020 г. 9:29
BahaMeirman

Здравстсвуйте Евгений, непонятно мне где эти исходники найти?
Сейчас обсуждают на форуме
19 февраля 2020 г. 11:58
Юрий

Спасибо, все понятно.
19 февраля 2020 г. 8:55
Михаиллл

Можно через сервер сделать
V
19 февраля 2020 г. 7:09
Vitali

Да, прямо сходу не заработало, а сейчас просто некогда разбираться, да и я уже решил не использовать в этом приложении WebEngine. Ошибка: WebEngineContext used before QtWebEngine::initialize(…
19 февраля 2020 г. 7:01
BlinCT

Просто реально не вижу тут каких то проблем в модели, вот вообще ничего. Но она все равно не отображается, то есть ладно бы если данные бы не появлялись а сама таблица была бы. Так и таблиц…
19 февраля 2020 г. 6:54
Михаиллл

понятно, думал что дебаг будет стандартно работать. спасибо за помощь!
EVILEG
О нас
Услуги
© EVILEG 2015-2019
Рекомендует хостинг TIMEWEB