Для django уже есть множество библиотек для кеширования и они уже обсуждалось на хабре, но, к сожалению, проблемы с производительностью не решить добавлением строчки в INSTALLED_APPS. В библиотеках патчащих queryset кеш инвалидируется либо слишком часто, либо слишком редко и самое главное у программиста мало контроля за этим процессом. Можно написать инвалидацию вручную, но потребуется много кода, в котором легко допустить ошибку.
По этой причине я написал маленький проект, в котором при добавлении объекта в кеш можно указать зависимости, при изменении которых кеш будет автоматически инвалидирован.
В качестве зависимости можно указать:
- Класс модели. При изменении/удалении любого объекта модели, вызова bulk_create, update у queryset'а этой модели запись в кеше будет инвалидирована.
- Инстанс модели. При изменении/удалении этого инстанса, запись в кеше будет инвалидирована.
- Related manger. При изменении любого дочернего объекта имеющего внешний ключ на указанный объект, запись в кеше будет инвалидирована.
Рассмотрим это на примере простого блога, у которого есть список всех постов и просмотр конкретного поста.
Для начала установим clever_cache.
$ pip instal django-clever-cache
Добавим ‘clever_cache’ в INSTALLED_APPS и укажем ‘clever_cache.backend.RedisCache’ в качестве бэкенда для кеша.
INSTALLED_APPS += ['clever_cache']
CACHES = {
"default": {
"BACKEND": 'clever_cache.backend.RedisCache',
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
'DB': 1,
}
}
}
Модели в нашем приложении-блоге выглядят следующим образом:
class Post(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=128)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = 'post'
verbose_name_plural = 'posts'
ordering = ['-created_at']
class Comment(models.Model):
author = models.ForeignKey('auth.User')
post = models.ForeignKey(Post, related_name='comments')
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = 'comment'
verbose_name_plural = 'comments'
ordering = ['-created_at']
Реализуем список всех постов. В запросе мы выбираем все посты, их авторов и количество комментариев к каждому посту, поэтому инвалидировать кеш нам придется при изменении любого поста, комментария или пользователя.
class PostListView(ListView):
context_object_name = 'post_list'
def get_queryset(self):
post_list_qs = cache.get('post_list_qs')
if not post_list_qs:
post_list_qs = Post.objects.all().select_related(
'author'
).annotate(comments_count=Count('comments'))
cache.set(
'post_list_qs',
post_list_qs,
depends_on=[Post, Comment, User]
# Запись в кеше зависит от моделей Post, Comment и User
)
return post_list_qs
Реализуем просмотр отдельного поста. Тут мы из базы данных получаем пост, его автора и отдельно комментарии к посту. Соответственно при изменении перечисленных объектов следует инвалидировать кеш.
class PostDetailView(DetailView):
model = Post
def get_context_data(self, **ctx):
post = self.get_post()
comments = self.get_comments(post)
ctx['post'] = post
ctx['comments'] = comments
return ctx
def get_post(self, *args, **kwargs):
pk = self.kwargs.get(self.pk_url_kwarg)
cache_key = "post_detail_%s" % pk
post = cache.get(cache_key)
if not post:
post = Post.objects.select_related('author').get(pk=pk)
cache.set(
cache_key, post,
depends_on=[post, post.author]
# при изменении поста или автора удалять запись из кеша
)
return post
def get_comments(self, post):
cache_key = "post_detail_%s_comments" % post.pk
comments = cache.get(cache_key)
if not comments:
comments = post.comments.all()
cache.set(
cache_key, comments,
depends_on=[post.comments]
# post.comments - это RelatedManager,
# при изменении любого комментария поста, кеш будет инвалидирован
)
return comments
Надеюсь, эта библиотека избавит вас от проблем с инвалидацией кеша и позволит сосредоточится на выборе имен переменных.
Код
Доступен на github
Автор: RafGbd