Для django уже есть множество библиотек для кеширования и они уже обсуждалось на хабре, но, к сожалению, проблемы с производительностью не решить добавлением строчки в INSTALLED_APPS. В библиотеках патчащих queryset кеш инвалидируется либо слишком часто, либо слишком редко и самое главное у программиста мало контроля за этим процессом. Можно написать инвалидацию вручную, но потребуется много кода, в котором легко допустить ошибку.
По этой причине я написал маленький проект, в котором при добавлении объекта в кеш можно указать зависимости, при изменении которых кеш будет автоматически инвалидирован.
В качестве зависимости можно указать:
Рассмотрим это на примере простого блога, у которого есть список всех постов и просмотр конкретного поста.
Для начала установим clever_cache.
Добавим ‘clever_cache’ в INSTALLED_APPS и укажем ‘clever_cache.backend.RedisCache’ в качестве бэкенда для кеша.
Модели в нашем приложении-блоге выглядят следующим образом:
Реализуем список всех постов. В запросе мы выбираем все посты, их авторов и количество комментариев к каждому посту, поэтому инвалидировать кеш нам придется при изменении любого поста, комментария или пользователя.
Реализуем просмотр отдельного поста. Тут мы из базы данных получаем пост, его автора и отдельно комментарии к посту. Соответственно при изменении перечисленных объектов следует инвалидировать кеш.
Надеюсь, эта библиотека избавит вас от проблем с инвалидацией кеша и позволит сосредоточиться на выборе имен переменных.
Доступен на github
По этой причине я написал маленький проект, в котором при добавлении объекта в кеш можно указать зависимости, при изменении которых кеш будет автоматически инвалидирован.
В качестве зависимости можно указать:
- Класс модели. При изменении/удалении любого объекта модели, вызова 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