loading...

Caching in Django REST Framework: Memcached

hasanul_islam profile image Hasanul Islam ・3 min read

To cache something is to save the result of an expensive calculation so that we don’t have to perform the calculation next time. We will discuss the caching in Django REST framework step by step:

Caching Concept

This is the pseudocode of the general caching concept:

get data from the cache using a key
if the key is in the cache:
    return the cached data
else:
    generate data
    save the generated data in the cache with a key(for next time)
    return the generated data

Setting up the Memcached :

Install Memcached :

We will install Memcached using docker image.

docker run -d memcached -p 11211:11211 -m 64

This would set the Memcached server to use 64 megabytes for storage running at 11211 port.

Install a Python Client :

We will install pylibmc, a python client to communicate with the Memcached server. But at first, we need to install the required dependencies of pylibmc,

For ubuntu or ubuntu-based docker image,

sudo apt-get install libmemcached-dev zlib1g-dev

For MacOS,

brew install libmemcached

Then install the PyPI package,

pip install pylibmc

Adding Cache Config in settings.py :

CACHE_HOST='127.0.0.1'
CACHE_PORT=11211
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': f'{CACHE_HOST}:{CACHE_PORT}',
    }
}

Caching API Response :

Suppose, We have a user app. We will have the following functionalities:

  • We will implement caching at user details API /api/user/{id}/.
  • Any request at this API will try to get from the cache. If no cached data exists for a user_id, user details data will be fetched from Database.
  • Any change or deletion on the User object will invalidate the cache. The following is the user details API,
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from django.core.cache import cache

from apps.user.models import User
from apps.user.serializers import UserSerializer

class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    def retrieve(self, request, pk=None):
        cache_key = f"user_details_{pk}"

        data = cache.get(cache_key)
        if data:
            return Response(data)

        response = super().retrieve(request, pk=pk)
        cache.set(cache_key, response.data)
        return response

Invalidating Cached Data :

We will invalidate the cached data using django signal.

Creating Signal Receivers :

We will add the following lines in user/signals.py,

def _invalidate_cached_data(user_id):
    cache_key = f"user_details_{pk}"
    cache.delete(cache_key)


def user_post_save_handler(sender, instance, created, **kwargs):
    if not created:
        _invalidate_cached_data(instance.id)


def user_post_delete_handler(sender, instance, **kwargs):
    _invalidate_cached_data(instance.id)

Connecting Receivers with Signals :

We will add the following line at user/apps.py inside user app,

from django.apps import AppConfig
from django.db.models.signals import post_save, post_delete


class UserAppConfig(AppConfig):
    name = "apps.user"

    def ready(self):
        from apps.user.models import User
        from apps.user.signals import (
            user_post_delete_handler,
            user_post_save_handler,
        )

        post_save.connect(user_post_save_handler, sender=User)
        post_delete.connect(user_post_delete_handler, sender=User)

Changing default_app_config :

We will add the following line at user/__init__.py in user app.

default_app_config = "apps.user.apps.UserAppConfig"

Feel free to leave a comment.

Posted on by:

Discussion

markdown guide