EVILEG-CORE. Caching properties of model objects using model_cached_property

ESNF-C, Django, model_cached_property, EVILEG

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
How to become an author?

Contribute to the evolution of the EVILEG community.

Learn how to become a site author.

Learn it
Donate

Good day, Dear Users!!!

I am Evgenii Legotckoi, developer of EVILEG. And it is my hobby project, which helps to learn programming another programmers and developers

If the site helped you, and you want also support the development of the site, than you can donate by following ways

PayPalYandex.Money
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 Timeweb
AS
May 26, 2020, 11:29 a.m.
Artem Sun-Dun-Chan

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

  • Result:50points,
  • Rating points-4
MN
May 25, 2020, 11:33 a.m.
Mitja Nagibin

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

  • Result:50points,
  • Rating points-4
f
May 25, 2020, 5:05 a.m.
falcon

C++ - Test 001. The first program and data types

  • Result:66points,
  • Rating points-1
Last comments
AL
May 29, 2020, 5:24 a.m.
Aleksandr Leonidov

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

Здравствуйте! После клика на ссылку авторизации выдает json словарь с ошибкой: {"error":"invalid_request","error_description":"redirect_uri is incorrect, check application redirect uri in …
May 28, 2020, 3:14 p.m.
Evgenij Legotskoj

Qt/C++ - Lesson 039. How to paint stroke in QSqlTableModel by value in the column?

Ну в моём примере, который в статье сработало так model->setData(model->index(1, 1), 7); Поскольку model->index(1, 0) - это индекс колонки id, которая скрыта, поэтому…
MA
May 28, 2020, 3:08 p.m.
Mihail A

Qt/C++ - Lesson 039. How to paint stroke in QSqlTableModel by value in the column?

Спасибо, завтра првоерю. А model->setData(model->index(1, 0), 7); Тоже заработало?
May 28, 2020, 3:06 p.m.
Evgenij Legotskoj

Qt/C++ - Lesson 039. How to paint stroke in QSqlTableModel by value in the column?

Да, метод data всё-таки влиял, я переписал его так и заработало удаление QVariant TableModel::data(const QModelIndex &idx, int role) const{ if (role == Qt::BackgroundColorRole) {…
May 28, 2020, 2:49 p.m.
Evgenij Legotskoj

Django - Tutorial 011. Adding comments to the site based on Django

Он более функциональный и его функционал объективно лучше поддерживается Django. Из первого, что приходит на ум: Это наличие полей типа Array Поддержка полей для JSON …
Now discuss on the forum
IP
May 29, 2020, 1:55 a.m.
Igor' Poroshin

QTablwView + QSqlQueryModel скрыть пустой столбец

Да, понятно. В данном случае лучше использовать серверную процедуру (если такие поддерживаются), в которой будет проверяться наличие всех пустых строк у нужного столбца и вызываться соответ…
RG
May 28, 2020, 6:21 p.m.
Rovshan Gurbanov

Сборка под старые версии Android

У меня SDK почти все версии есть, NDK есть версии 10, 17, 21. Но собирается приложение только с NDK v21 под Android версии 7.0 и выше Версия Qt у меня 5.14.2
May 28, 2020, 7:58 a.m.
Evgenij Legotskoj

Освобождение памяти QMainWindow::setCentralWidget

Да, соглашусь. Просто удаление происходит позже, а не сразу.
May 28, 2020, 5:43 a.m.
Mihailll

При подключении к git как указать пароль?

Нужно сделать ssh-keygen и потом полученый из файла код скопировать в ssh ключ в бикбакете
F
May 28, 2020, 1:42 a.m.
Fidan

QML

Да, проблема ушла, спасибо.
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB