Евгений Легоцкой3 ноября 2019 г. 15:03

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

Содержание

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

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

Комментарии

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

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
PH

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60баллов,
  • Очки рейтинга-1
СН

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:10баллов,
  • Очки рейтинга-10
k
  • knobu
  • 23 сентября 2020 г. 12:34

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

  • Результат:60баллов,
  • Очки рейтинга-1
Последние комментарии
ДИ

Qt/C++ - Урок 015. QTableWidget или Как сделать таблицу с чекбоксами

Кто-нибудь знает, как сделать так, чтобы в QTableWidget состоящей из чекбоксов в строке таблицы можно было выбрать только один checkbox ?

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

QSqlTableModel выполняет ряд стандартных операций для одной таблицы из базы данных. Поэтому там и реализован функционал по удалению и редактированию. QSqlQueryModel позволяет выполнить запр…
VB

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

Добрый день. Хотел спросить вот что. Создал проект на основе QAbstractTableModel. В MainWindow cоответственно создал модель и связал с представлением. Поиск веду по списку элементов модели,…

QCheckBox в качестве делегата QTableView

До тех пор, пока у вас проект содержит только одну таблицу, или несколько то может быть. Когда их будет 1000 и чекбоксы в разных колонках, то без делегатов и переопределения возвращаемых ре…
D
  • Damir
  • 21 сентября 2020 г. 1:34

QCheckBox в качестве делегата QTableView

bool Node::setData(const QModelIndex& index, const QVariant& value, int role){ switch (index.column()) { case 0: switch (role) { case Qt::CheckStateRole:// <- т…
Сейчас обсуждают на форуме

Как в Qt в qmenu добавить scrollarea

Вот это наследованный класс меню. Но посути это обычное меню. #pragma once#include <QtWidgets>class TransMenu : public QMenu { Q_OBJECTpublic: TransMenu(QWidget* parent = …
  • Nomad
  • 1 октября 2020 г. 15:22

MyForm(forms.Form): - непонятка

понятно спасибо
ДИ

Как в QTableWidget, состоящей из чекбоксов, в строке таблицы можно было выбрать только один checkbox ?

Сделал таблицу состоящую из чекбоксов по уроку https://evileg.com/ru/post/78/ Кто-нибудь знает, как сделать так, чтобы в QTableWidget состоящей из чекбоксов в строке таблицы можно …
U

как скрыть елемент с копии виджета

Удалить пост нельзя... Поэтому удачки, Катту)
H

Тесты падают при сборке под MinGW.

Всем привет! При сборке под MinGW некоторые тесты при старте выдают FATAL "не удалось запустить тест проекта" и подвешивают Qt Creator - приходится его перезапускать. При сборке под MSVC та…
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB