Um die Website zu beschleunigen, können Sie neben der Optimierung von Datenbankabfragen auch Caching verwenden.
Django erlaubt Caching:
- individuelle Ansicht , sowohl klassenbasierte Ansicht als auch normale Ansicht -Funktionen
- ganze Vorlagen oder Teile dieser Vorlagen
- Abfragesatz
- sowie Eigenschaften von Modellobjekten mit cached_property
Ich war an der Möglichkeit interessiert, einzelne Eigenschaften von Modellobjekten für umfangreiche Berechnungen oder umfangreiche Datenbankabfragen zwischenzuspeichern.
Der Decorator
cached_property
hat eine solche Funktionalität, aber das Minus für mich war, dass das Caching nur für die Lebensdauer des Objekts erfolgte.
Während ich das Caching für einen längeren Zeitraum benötige als die Existenz des Objekts, wenn die Seite angefordert wird. Außerdem musste ich Eigenschaften abhängig von den Eingabeargumenten zwischenspeichern. Dieser Site-Decorator speichert die Anzahl der Vorlieben und Abneigungen sowie Informationen darüber, ob dem aktuellen Benutzer ein bestimmtes Inhaltselement gefallen hat.
So wurde der Decorator model_cached_property geschrieben
model_cached_property
Dieser Decorator verwendet redis als Caching-Mechanismus, da das Ungültigmachen des Caches möglicherweise das Löschen der Gruppe von Schlüsseln erfordert, die zu dieser Eigenschaft gehören. Da die Eigenschaft für verschiedene Benutzer auf unterschiedliche Weise zwischengespeichert werden kann.
Installation von EVILEG-CORE
pip install evileg-core
Außerdem zieht evileg_core alle für dieses Paket erforderlichen Abhängigkeiten ein. Einschließlich der Bibliothek django-redis , die verwendet wird, um mit redis zu arbeiten.
Wenn Sie redis noch nicht verwenden, müssen Sie es installieren.
sudo apt install redis-server
settings.py
evileg_core zu installierten Anwendungen hinzufügen
INSTALLED_APPS = [ ... 'evileg_core', ]
Einrichten eines Caching-Backends
CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } }
Mit model_cached_property
Normales 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()
Dieser Decorator speichert die Kommentaranzahl des Artikels für 60 Sekunden. Bei der nächsten Anforderung einer Artikelseite erfolgt zunächst die Anforderung an den Cache für das jeweilige Artikelobjekt, und erst wenn der Cache abgelaufen ist, erfolgt erneut eine Abfrage an die Datenbank.
Caching-Timeout festlegen
Wenn Sie eine bestimmte Cache-Zeit festlegen möchten, können Sie das timeout -Argument mit einem Decorator verwenden.
class Article(models.Model): @model_cached_property(timeout=3000) def comments_count(self): return self.comments.count()
Einstellung der globalen Cache-Dauer
Sie können auch eine globale Cache-Zeit für alle Decorators in settings.py in Sekunden festlegen.
MODEL_CACHED_PROPERTY_TIMEOUT = 300000
Eigenschaften mit Argumenten verwenden
Und jetzt das Interessanteste. Zwischenspeichern von Eigenschaften mit Argumenten, sodass Sie das Ergebnis unter Verwendung von Informationen zu den Eingabeargumenten der Datenmodelleigenschaft zwischenspeichern können.
Dies ist sowohl ein Vorteil dieses Dekorateurs als auch ein Nachteil. Tatsache ist, dass das Caching korrekt ist, es ist notwendig, dass die Eingabeargumente eindeutig sind. Wenn die Eingabeargumente beispielsweise temporäre nicht eindeutige Objekte wie AnonymousUser sind, funktioniert das Caching nicht.
Je nach Benutzer kann jedoch ein Decorator zum Caching verwendet werden. So könnte es aussehen.
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
Beachten Sie, dass es eine Prüfung auf user.is_authenticated gibt, da das Zwischenspeichern eines nicht authentifizierten Benutzers nicht korrekt funktioniert, aber für einen authentifizierten Benutzer wird es korrekt funktionieren, da der authentifizierte Benutzer ein eindeutiges Objekt ist.
Cache-Invalidierung
Für den Fall, dass der Cache vor Ablauf seiner Lebensdauer irrelevant geworden ist, können Sie die Funktion invalidate_model_cached_property verwenden
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)
Um den Cache korrekt zu invalidieren, müssen Sie das Objekt übergeben, dessen Cache Sie löschen möchten, sowie die Methode dieses Objekts.
Es kann genauso aussehen.
article = get_object_or_404(Article, pk=12) invalidate_model_cached_property(article, article.comments_count)
Fazit
Daher kann model_cached_property verwendet werden
- Caching-Eigenschaften von Modellobjekten für lange Zeit, längere Objektlebensdauer beim Anfordern einer Seite
- Caching-Eigenschaften von Modellobjekten in Abhängigkeit von Eingabeargumenten
Es gibt Einschränkungen
- Dieser Decorator kann nur in Modellen verwendet werden, d. h. Klassen, die von models.Model geerbt wurden
- Das Zwischenspeichern von Modelleigenschaften in Abhängigkeit von Argumenten sollte nur für eindeutige Eingabeargumente erfolgen, da das Zwischenspeichern sonst nicht korrekt ist
а 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
Всё конечно супер классно. Интересует лишь один вопрос.
Здравствуйте. В общем меня интересует такой вопрос. Я пробовал это на Like , Dislike. Как я понимаю если не перевалидировать кеш то ничего не изменится на странице. Вернётся значение из кэша? Отсюда второй вопрос как и где прописть эту перевалидацию? Ибо если я пишу её через модель как сказано в статье. То ничего не происходит. По этому интересует то как и где перевалидировать кеш.
Да, если не вызывать invalidate_cache , то ничего не произойдёт.
Место вызова инвалидации обычно индивидуально. Но я стараюсь вешать его на сигналы сохранения и удаления объектов.
В случае с Like Dislike , которые используют GenericForeignKey удобнее всего поступить следующим образом.
Добавить метод invalidate_cache , который будет вызывать метод инвалидации content_object
А потом навешать инвалидацию на сигналы
В таком случае инвалидация будет действовать на лету, главное, чтобы во всех моделях были, к которым привязываются лайки и дизлайки, были определены соответсвующие методы инвалидации.
Спасибо за пояснения. Я ток щас догнал как это всё работает. Просто с сигналами не работал никогда. Но вот теперь стало понятно.