Computer scienceBackendDjangoCaching

Redis basics

5 minutes read

If you're looking for fast data storage and retrieval for your website, consider Redis. Unlike traditional relational databases like MySQL or PostgreSQL, Redis is an in-memory database that can handle large amounts of data quickly and efficiently. Not only that, but it's also highly scalable, making it an excellent choice for managing vast amounts of data. Redis is a database that uses a key-value data structure. It can be used as a cache, database, or message broker. Let's talk about the basics of Redis and how we can use it to create and deploy high-performance and scalable systems.

To install Redis on Linux, run the command:

sudo apt install redis-server

For Windows: download the distribution kit from the official site.

If you are familiar with Docker, you can use Redis in a Docker container. Also, playing around with Redis directly without installing anything using a sandbox environment is possible.

This will install the Redis server and a few helpful command-line tools. By default, Redis uses the 6379 port.

SET and GET

Since Redis is based on a key-value data structure, the basic operations include assigning a value to a key and getting a value by a key. To do this, Redis uses the set and get commands.

Set is an operation that sets a value to a specified key. For example, if we want to store a username in Redis, we can use the command in redis-cli:

set username Alice

This command will create the key username and assign it the value Alice. If we want to get this value, we can use the get command:

get username

This command will return the value Alice. In addition, to work with Redis within Python, we can use the Redis library. It can be installed by using the following command:

pip install redis

You can use the library in your IDE:

import redis
r = redis.Redis()
r.set('username', 'Alice')
value = r.get('username')
print(value) # returns b"Alice"

Note that the values are returned as byte strings so use the decode method to convert them to regular strings:

r.get('username').decode() # returns string "Alice"

These commands would do the same as the commands to redis-cli before that. That's it. While the set and get commands are very simple; it is basically what you need to save and retrieve data. As already said, by default, Redis uses the 6379 port, but you can change this value by changing the port argument:

import redis
r = redis.Redis(host='localhost', port=31337)

In addition, it is important to note that Redis provides the ability to configure the storage time of information using the expire, ttl and resist commands. Expire sets the key's expiration date in seconds, ttl returns the remaining key lifetime in seconds, and persist makes the key permanent, meaning it cancels its expiration.

r.expire('username', 60) # set the expiration date of the 'username' key to 60 seconds
r.ttl('username') # get remaining lifetime of key 'username'
r.persist('username') # make the 'username' key permanent

While we used a short value of Alice for the key Alice, Redis excels in efficiently storing more extensive data, such as entire static HTML pages, essentially long strings, that can be used as server responses. If you need to store multiple values under the same key, Redis offers versatile collections, so let's delve further into collections.

Collections in Redis

In addition to strings, Redis offers a wide range of data types for working with ordered and unordered collections. While Redis includes advanced types, which can technically be considered collections, our focus in this discussion will be the two most commonly used ones: sets and lists.

Sets are unordered collections of unique strings. Because sets contain unique values, they can be useful when duplicates must be avoided. Sets have no order, so values are either retrieved randomly or driven by the content of the values themselves. Redis commands for working with sets allow you to add, remove, check for, and retrieve elements from a set and perform set operations such as union, intersection, and difference.

To add one or more elements to a set, you can use the sadd method, passing it the set key and the elements. The method returns the number of added elements:

res = r.sadd('usernames', 'Alice', 'Alex', 'Sam') # 3
# the set is {'Alice', 'Alex', 'Sam'}

To remove one or more elements from a set, you can use the srem method, passing it the set key and the elements. The method returns the number of elements removed:

res = r.srem('usernames', 'Alex', 'Sam') # 2
# the set is {'Alice'}

To check whether an element belongs to a set, you can use the sismember method, passing it the set key and the element. The method returns 1 if the element is present in the set and 0 if not:

res = r.sismember('usernames', 'Alex') # 0
res = r.sismember('usernames', 'Alice') # 1

To get all the elements of a set, you can use the smembers method, passing it the set key. The method returns a list of elements in random order:

res = r.smembers('usernames') # {'Alice'}

To obtain the size (cardinality) of a set, you can use the scard method, passing it the set key. The method returns an integer equal to the number of elements in the set:

res = r.scard('usernames') # 1

Lists in Redis store ordered collections of strings. Lists are ordered in the order in which elements are added, which can occur from the list's beginning or end. Adding an element to the end of the list is done using the rpush command, and to the beginning with lpush:

r.rpush("usernames", "Katherine") # adds "Katherine" to the end of the list
r.rpush("usernames", "Alice") # adds "Alice" to the end of the list
r.lpush("usernames", "Steve") # adds "Steve" to the beginning of the list
# the list is ['Steve', 'Katherine', 'Alice']

Retrieving elements from a list using the lpopand rpop methods. These methods remove and return elements from the beginning or end of a list:

r.lpop("usernames") # returns and removes the first element of a list
r.rpop("usernames") # returns and removes the last element of a list
# the list is ['Katherine']

Removing elements from a list using the lrem method. This method removes a specified number of occurrences of an element equal to a given value from a list. For example, if we want to remove all "Alex" items from the "usernames" list, we can use the command:

r.lrem("usernames", 0, "Alex") # removes all occurrences of "Alex" from the list

The count argument is 0, which means removing all matching elements. If count was positive, then the command would remove the corresponding number of elements from the beginning of the list, and if count was negative, then the command would remove elements from the end of the list.

Searching for elements in a list can be done with the lindex, lrange methods. These methods return an element by index or a range of elements by index. For example, if we want to get the third element or the first two elements from a list, we can use the commands:

r.lindex("usernames", 2) # returns the third element of the list
r.lrange("usernames", 0, 1) # returns the first two elements by index

If we want to keep only the first two elements in the usernames list, we can use the ltrim command:

r.ltrim("usernames", 0, 1)

It is also possible to change elements in the list using lset methods:

r.lset("usernames", 1, "Kris") # sets the value of the second element to "Kris"

Sets and lists can be inconvenient for indices, but Redis has a type for named fields.

Hashes

Hmaps in Redis are hashes that allow you to store key-value pairs in memory. Hmaps support various operations such as creating, updating, deleting, and searching for elements by key or value. For example, if we want to create a hash with the name user and add the fields name, age and email with the corresponding values, we can use the following code:

import redis
r = redis.Redis()
r.hset("user", "name", "Alice") # adds the "name" field with the value "Alice" to the "user" hash
r.hset("user", "age", 25) # adds the "age" field with the value 25 to the "user" hash
r.hset("user", "email", "[email protected]") # adds the "email" field with the value "[email protected]" to the "user" hash

Note that hashes are like Python dictionaries with no nesting allowed, and all keys are strings.

If we want to get a value for a specific key from a hash, we can use the hget method:

r.hget("user", "name") # returns "Alice"
r.hget("user", "age") # returns 25
r.hget("user", "email") # returns "[email protected]"

If we want to get all the fields and values from the hash, we can use the hgetall method:

r.hgetall("user") # returns dictionary {"name": b"Alice", "age": b"25", "email": b"[email protected]"}

If we want to change or remove a key value from a hash, we can use the hset and hdel methods:

r.hset("user", "age", 26) # changes the value of the "age" field to 26 in the "user" hash
r.hdel("user", "email") # removes the "email" field from the "user" hash

As you can see, there are many commands, but you might have noticed that there is a rule that can help you: commands starting with l and r are dealing with lists (left and right), starting with s are probably related to sets, and those starting with h are related to hashes.

Using Redis with Django

Now that we understand the basic types of stored data and the commands we can use, it's time to learn how to use Redis with Django.

Django pages are stored using Redis as key-value pairs, where the key is the page's unique identifier, and the value is the serialized HTML of the page. When a user requests a page, Django first checks to see if it is in the Redis cache. If there is, Django returns it from the cache without accessing the database or template engine. If not, Django generates the page and stores it in the Redis cache for the specified time. To work with Redis in Django, you need to configure the CACHES parameter in the settings.py file of your project by specifying RedisCache as BACKEND and redis-py URL as LOCATION:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
    }
}

To cache views, you can use the cache_page decorator:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    # view code

When you apply the cache_page decorator to a view in Django, you specify a cache lifetime in seconds. Then, when the user requests this page, Django checks to see if it is in the Redis cache. If so, Django returns the page from the cache without accessing the database or performing presentation logic. If not, Django generates the page, stores it in the Redis cache for the specified time, and returns it to the user.

Additionally, you can use the {% cache %} and {% endcache %} tags to cache template fragments:

{% load cache %}
{% cache 3600 sidebar request.user.username %}
    <div class="sidebar">
        <!-- sidebar code -->
    </div>
{% endcache %}

This example caches the sidebar for one hour per user. When a user requests a page, Django checks whether the sidebar fragment is in the Redis cache using the sidebar_username key. If so, Django returns the fragment from the cache without going to the database or executing template logic. If not, Django generates the fragment, stores it in the Redis cache for one hour, and returns it to the user.

Caching framework in Django is rich and deserves a separate topic, and you can read about it (and about using Redis for it) in the official documentation.

Conclusion

Redis is a useful in-memory data tool that supports various data types, such as:

  • Strings, which are sequences of characters, for example, Hello world;
  • Sets, which are collections of unique and unordered strings, for example, {'1', '2', '3'}
  • Lists, which are ordered sequences of strings, for example, ['1', '2', '3', '1']
  • Hashes, which are mappings of fields and values, for example, {'name': 'John', 'age': 25}

Redis can be integrated into Django applications. Redis is very popular and maybe even more popular than Python, so likely any web framework you're dealing with that uses Python has a way to interact with Redis, and even if it does not, you can use Redis-py directly to work with Redis instance. Using Redis with Django, you can improve the performance and reliability of your web application.

How did you like the theory?
Report a typo