Evgenij LegotskojNov. 3, 2019, 4:03 a.m.

EVILEG-CORE. Caching properties of model objects using model_cached_property

Content

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.
- company blog
Support the author Donate
MK

а 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

Comments

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

Let me recommend you the excellent hosting on which EVILEG is located.

For many years, Timeweb has been proving his stability.

For projects on Django I recommend VDS hosting

View Hosting
VS

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

  • Result:14points,
  • Rating points-10
VS

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

  • Result:14points,
  • Rating points-10
IP

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

  • Result:10points,
  • Rating points-10
Last comments
A
  • Andrey
  • April 28, 2021, 1:25 a.m.

Django - Tutorial 036. How to add authentication through social networks. VKontakte

после того как дам разрешение для просмотра моего емайл, вот такая ошибка: AuthForbidden at /social-auth/complete/vk-oauth2/ Your credentials aren't allowed Вот сетингс: SOC…
DV

Qt/C++ - Lesson 051. QMediaPlayer – simple audio player

Добрый вечер. Хотел бы получить консультацию по работе с проектом на Mac OS. Открыл проект в QT и собрал его. Проблема в том, что он не воспроизводит треки и их названия зацикленно мелькают в по…
SS

Как соответствовать новым требованиям Google Play

Добрый день. Спасибо вам огромное за вашу статью! только начинаю изучать QT под Андроид 4 дня потратил на то чтобы подобрать версию QT которая наконец то скомпилирует мне на windo…
YA

PyQt5 - Tutorial 009. Using QThread with MoveToThread

Hello. Let's say I want to send some variables to "run" define. How can we do that? I modified your code, I tried something like below, but the GUI is frozen that way. I could not be able to und…
R

Распознавание изображений на Python с помощью TensorFlow и Keras

почему то вместо 50000 обрабатывает по 782 картинки кажду. эпоху
Now discuss on the forum

Потеря данных в сигнал/слот

вопрос решен
M

Qt/QML/Android(navigation button) - Стандартные клавиши навигации Андроид

Здравствуйте, пытаюсь заставить работать стандартную андроид-клавишу "назад", пытался разными способоами, они приведены в закомментированном коде. В том числе использовал https://evileg.com/ru/f…
M

Как включить OpenGL в Adnroid эмуляторе QtCreator

Здравствуйте, у меня работают виртуальные машины с Android, но только в дефолтной был включен OpenGL. Потом я удалил дефолтную, создал новые, а в них OpenGL отключен. Справа на втором скриншоте …

QScrollArea dynamically add QCheckBoxes

Всё правильно. Это просто спамер, который отправился в вечный бан.

qml зажатая кнопка мыши в одной MouseArea и сигналы мыши из другой MouseArea

добрый, вы не пробовали отслеживать область видимости мышки через: _mouseArea.containsMouse и когда мышка будет в другой зоне видимости обрабатывать ее состояния?
About
Services
© EVILEG 2015-2021
Recommend hosting TIMEWEB