📜 ⬆️ ⬇️

And again about caching in Django

For django, there are already many libraries for caching and they have already been discussed on Habré , but, unfortunately, performance problems cannot be solved by adding a line to INSTALLED_APPS. In libraries patching queryset cache is invalidated either too often or too rarely and most importantly the programmer has little control over this process. You can write invalidation manually, but you will need a lot of code in which it is easy to make a mistake.

For this reason, I wrote a small project in which when you add an object to the cache, you can specify dependencies, if you change which, the cache will be automatically invalid.

As a dependency, you can specify:

  1. Class model When changing / deleting any object of the model, calling bulk_create, update in the queryset of this model, the entry in the cache will be invalid.
  2. Instance model. When changing / deleting this instance, the cache entry will be invalid.
  3. Related manger. When changing any child object having a foreign key to the specified object, the entry in the cache will be invalid.

Consider this on the example of a simple blog, which has a list of all posts and viewing a particular post.
')
First, install clever_cache.

$ pip instal django-clever-cache 

Add 'clever_cache' to INSTALLED_APPS and specify 'clever_cache.backend.RedisCache' as a backend for the cache.

 INSTALLED_APPS += ['clever_cache'] CACHES = { "default": { "BACKEND": 'clever_cache.backend.RedisCache', "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { 'DB': 1, } } } 

Models in our blog app look like this:

 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'] 

We implement the list of all posts. In the request, we select all posts, their authors and the number of comments for each post, so we will have to disable the cache when changing any post, comment or user.

 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 

We implement viewing a separate post. Here we get the post, its author and separate comments to the post from the database. Accordingly, when changing the listed objects, the cache should be invalidated.

 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 

I hope this library will save you from problems with cache invalidation and allow you to focus on the choice of variable names.

Code


Available on github

Source: https://habr.com/ru/post/337998/


All Articles