How to Cache Using Redis in Django Applications

One of the ways to relieve strain on a server is by caching data. This is done by caching data after it has been processed and then serving it from the cache the next time it is requested. This tutorial will give a detailed discussion of Redis, explaining how to install Redis and cache data in Python applications.

Introduction to Redis and Caching

Caching refers to storing the server response in the client itself, so that a client need not make a server request for the same resource again and again. A server response should have information about how caching is to be done, so that a client caches the response for a time period or never caches the server response.

A cache, on the other hand, is a hardware or software component that is used to store data so future requests for the same data can be served faster.

In this age where users expect results within a second, it is wise to serve requests by reading data from the cache, which is ultimately faster than reading from a slower data store; thus, the system performance depends on how many requests can be served from the cache.

Redis is an open-source, in-memory data structure store, used as a database, cache, and message broker. It works by storing data in a cache and providing it the next time it is requested, instead of querying the database every time.

Install Redis

The first step is to get Redis up and running locally on your machine. The simplest way to install Redis is via the operating system’s package manager like so:

sudo apt-get install redis-server

You can also follow the instructions from the official Redis site.

Download and extract Redis 4.0.6 tar as follows:

$ wget http://download.redis.io/releases/redis-4.0.6.tar.gz 
$ tar xzf redis-4.0.6.tar.gz 
$ cd redis-4.0.6 $ make

The binaries that are now compiled are available in the src directory. Run Redis with:

$ src/redis-server

You can interact with Redis using the built-in client:

$ src/redis-cli
redis set foo bar OK redis
get foo "bar"

To check if the redis server is running, issue the following command on the terminal:

$ sudo redis-server
* Ready to accept connections

Django API Example

Let’s create our Django project. Our project will be able to cache all the products in a store, making it easy and fast to retrieve data in subsequent queries.

To make use of Redis in our app, we need to do the following:

  1. Check if results for the current query exist in the cache.
  2. If results exist in the cache, retrieve them.
  3. If results don’t exist, fetch them, store them in the cache, and then forward them to the requesting entity.

Requirements

  • Django
  • django-redis
  • Redis
  • loadtest

Create Your Project

Before we get started, create a directory and install a virtual environment. A virtual environment will let you install library versions required by your application.

mkdir myprojects

cd myprojects

Next, activate the virtual environment and install the project requirements.

 source venv/bin/activate
 
 pip install django==1.9
 
 pip install django-redis
 
 pip install djangorestframework

Create a Django Project

django-admin startproject django_cache

Create a new app called store, which will handle product management in our store.

cd django_cache

python manage.py startapp store

Add the store application and rest_framework to the list of installed apps in the settings.py file.

# settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'store', # add here
    'rest_framework', # add here too
]

Creating the Models

In store/models.py, we start by creating the Product model for storing the product details as follows:

from __future__ import unicode_literals
from django.db import models
import datetime

# Create your models here.


class Product(models.Model):

    name = models.CharField(max_length=255)
    description = models.TextField(null=True, blank=True)
    price = models.IntegerField(null=True, blank=True)
    date_created = models.DateTimeField(auto_now_add=True, blank=True)
    date_modified = models.DateTimeField(auto_now=True, blank=True)

    def __unicode__(self):
        return self.name

    def to_json(self):
        return {
            'id': self.id,
            'name': self.name,
            'desc': self.description,
            'price': self.price,
            'date_created': self.date_created,
            'date_modified': self.date_modified
        }

Migrations

Create an initial migration for our products model, and sync the database for the first time.

python manage.py makemigration store

python manage.py migrate

Create superuser

Create a superuser, log in to the admin panel, and populate your database with some sample data which we will use to do our tests.

python manage.py createsuperuser

Configuring Redis in Python Applications

In order to use Redis with a Django application, we need to set up Redis to store the application’s cache data. And the following to your settings.py file:

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

Next, we are going to create an endpoint that retrieves all the products from our database. We will first test the performance of the application in terms of how long it takes to retrieve data from the database without caching it. We will then implement another endpoint that retrieves data from a cache and compare the performance.

In store/views.py, add the following code which retrieves all the products present in the database.

from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status

# Create your views here.



@api_view(['GET'])
def view_books(request):

    products = Product.objects.all()
    results = [product.to_json() for product in products]
    return Response(results, status=status.HTTP_201_CREATED)

Configuring URLs

Create a file store/urls.py and add the following code.

# store/urls.py
from django.conf.urls import url
from .views import view_books


urlpatterns = [
    url(r'^$', view_books),
]

We also need to import URLs from the users application to the main django_cache/urls.py file.

# django_cache/urls.py

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^store/', include('store.urls'))
]

Let’s do a test and see if we are on track. We will be using loadtest. If you are not familiar with loadtest, it’s a tool for testing performance.

Installing loadtest as root is simple:

sudo npm install -g loadtest
$ loadtest -n 100 -k  http://localhost:8000/store/

# result
INFO Requests per second: 55

As seen from the above, 55 requests are processed per second.

Let’s create another endpoint for retrieving data after caching with Redis. Edit users/views.py to match the following:

from rest_framework.decorators import api_view
from rest_framework import status
from rest_framework.response import Response
from django.core.cache import cache
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT

CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)
from .models import Product


# Create your views here.


@api_view(['GET'])
def view_books(request):
    # rest of the code


@api_view(['GET'])
def view_cached_books(request):
    if 'product' in cache:
        # get results from cache
        products = cache.get('product')
        return Response(products, status=status.HTTP_201_CREATED)

    else:
        products = Product.objects.all()
        results = [product.to_json() for product in products]
        # store data in cache
        cache.set(product, results, timeout=CACHE_TTL)
        return Response(results, status=status.HTTP_201_CREATED)

The code above will check if the key product is present in the cache, and if found, the data represented will be returned to the browser. In the event that no data is present in the cache, we first retrieve the data from the database, store it in the cache, and then return the data queried to the browser.

Update store/urls.py as follows.

from django.conf.urls import url
from .views import view_books, view_cached_books


urlpatterns = [
    url(r'^$', view_books),
    url(r'^cache/', view_cached_books),

]

Let’s carry out the tests.

$ loadtest -n 100 -k  http://localhost:8000/store/cache/

# results
INFO Requests per second: 233


The first time you hit the endpoint localhost:8000/store/cache, the application will query from the database and return data, but subsequent calls to the URL will bypass the database and query from the cache since the data is already available in the cache.

Conclusion

In this tutorial, we used Redis to give an application the illusion of speed. We leverage the use of RAM in Redis to store the results of queries and then return those results from the cache in subsequent queries rather than doing the round trip to the database.

There are other caching tools available, such as Memcached, which is similar to Redis. However, Redis is more popular than Memcached because it takes only a few minutes to set up and get working in applications. Redis has more sophisticated mechanisms as it has been described as a “data structure store”, thus making it more powerful and flexible. Redis also has a larger advantage because you can store data in any form.

Hopefully, this tutorial has shown you how easy it is to add a caching layer to your application, hence improving performance. Caching must be something to consider when you need to reduce loading times and server costs.

Leave a Reply

Your email address will not be published. Required fields are marked *