Evgenii Legotckoi
Evgenii LegotckoiNov. 3, 2019, 4:03 a.m.

EVILEG-CORE. Caching properties of model objects using model_cached_property

To speed up the site, in addition to optimizing database queries, you can use caching.

Django allows you to cache:

  • individual view , both Class Based View , and ordinary functions view
  • whole templates or parts of these templates
  • QuerySet
  • as well as properties of model objects using cached_property

I was interested in the ability to cache individual properties of model objects for heavy computing or heavy database queries.
The cached_property decorator has such a functional, but the drawback for me was that caching occurred only for the lifetime of the object.
Whereas I need caching for a longer period of time than the existence of an object when requesting a page. And also I needed to cache properties depending on the input arguments. This decorator on the site caches the number of likes and dislikes, as well as information about whether the current user liked a particular content object.

Thus the decorator model_cached_property was written


model_cached_property

This decorator uses redis as a caching backend, because invalidating the cache may require deleting the group of keys that belong to this property. Since the property can be cached for different users in different ways.

Intall EVILEG-CORE

pip install evileg-core

Also evileg_core will pull up all the dependencies necessary for this package. Including the django-redis library, which is used to work with redis .

If you are not using redis yet, you will need to install it.

sudo apt install redis-server

settings.py

Add evileg_core to installed applications

INSTALLED_APPS = [
    ...
    'evileg_core',
]

Configuring the caching backend

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

Using model_cached_property

Normal caching

from evileg_core.cache.decorators import model_cached_property

class Article(models.Model):

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

This decorator will cache the number of comments on the article for 60 seconds. At the next request for the article page, the request will be executed first to the cache for a specific article object, and only if the cache has expired, the request will again be executed to the database.

Set Caching Timeout

If you want to set a specific caching time, you can use the timeout argument with the decorator.

class Article(models.Model):

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

Global setting of cache duration

You can also set the global caching time for all decorators in settings.py in seconds

MODEL_CACHED_PROPERTY_TIMEOUT = 300000

Using properties with arguments

And now for the fun part. Caching properties with arguments, thanks to which you can cache the result using information about the input arguments of the property of the data model.

This is both an advantage of this decorator and its disadvantage. The thing is that caching is correct, it is necessary that the input arguments are unique. For example, if the input arguments are temporary non-unique objects, like AnonymousUser , then caching will not work.

However, the decorator can be used for caching depending on the user. It might look like this.

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

Please note that there is a check for user.is_authenticated , because caching of an unauthenticated user will not work correctly, but it will work correctly for an authenticated user, since the authenticated user is a unique object.

Cache invalidation

In the event that the cache has become irrelevant before the expiration of its life, then you can use the function 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)

For correct invalidation of the cache, you must pass the object whose cache you want to clear, as well as the method of this object.

It may look the same way.

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

Conclusion

Thus model_cached_property can be used to

  • caching properties of model objects for a long time, more than the lifetime of the object when requesting a page
  • caching properties of model objects depending on input arguments

There are limitations

  • this decorator can only be used in models, that is, classes inherited from models.Model
  • caching of model properties depending on the arguments should be performed only for unique input arguments, otherwise caching will not be correct
We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

МК
  • Nov. 6, 2019, 2:25 a.m.

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

Evgenii Legotckoi
  • Nov. 6, 2019, 3:16 a.m.
  • (edited)

Добрый день.

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

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

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

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

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

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

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

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

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

NSProject
  • May 27, 2022, 6:27 a.m.
  • (edited)

Всё конечно супер классно. Интересует лишь один вопрос.

NSProject
  • May 28, 2022, 10:20 a.m.

Здравствуйте. В общем меня интересует такой вопрос. Я пробовал это на Like , Dislike. Как я понимаю если не перевалидировать кеш то ничего не изменится на странице. Вернётся значение из кэша? Отсюда второй вопрос как и где прописть эту перевалидацию? Ибо если я пишу её через модель как сказано в статье. То ничего не происходит. По этому интересует то как и где перевалидировать кеш.

Evgenii Legotckoi
  • May 30, 2022, 10:25 a.m.

Да, если не вызывать invalidate_cache , то ничего не произойдёт.
Место вызова инвалидации обычно индивидуально. Но я стараюсь вешать его на сигналы сохранения и удаления объектов.

В случае с Like Dislike , которые используют GenericForeignKey удобнее всего поступить следующим образом.

Добавить метод invalidate_cache , который будет вызывать метод инвалидации content_object

class LikeDislike(...):

    ...

    def invalidate_cache(self):
        self.content_object.invalidate_like_dislike_cache()

А потом навешать инвалидацию на сигналы

# -*- coding: utf-8 -*-

from django.db.models.signals import post_save, pre_delete

from .models import LikeDislike

def cache_invalidate_activity(sender, instance, **kwargs):
    instance.invalidate_cache()

post_save.connect(cache_invalidate_activity, sender=LikeDislike)
pre_delete.connect(cache_invalidate_activity, sender=LikeDislike)

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

NSProject
  • June 2, 2022, 9:33 a.m.

Спасибо за пояснения. Я ток щас догнал как это всё работает. Просто с сигналами не работал никогда. Но вот теперь стало понятно.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
AD

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:80points,
  • Rating points4
m

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:20points,
  • Rating points-10
Last comments
ИМ
Игорь МаксимовNov. 23, 2024, 12:51 a.m.
Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiNov. 1, 2024, 2:37 a.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 8:19 p.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 7:51 p.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 11:02 p.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 25, 2024, 3:11 a.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 7:04 p.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 3:49 p.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 9:10 p.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks