Using Redis Memory Storage for your Python

Published on Aug. 22, 2023, 12:13 p.m.

Why Redis ?

Why might I choose to proverbially shit on NoSQL databases prior to advocating for Redis?MongoDB stores records on disk space like SQL databases.Allocating disk space to store a record comes with the implication that the information being stored is intended to persist.

Redis is an in-memory database that stores values as key/value pairs.Reading and writing to memory is faster than writing disk. Memory is suitable for storage secondary data.If we decide at a later time that data in memory is worth keeping (such as a SQL database) later on.

In-memory data stores like Redis or Memcached lie somewhere between your app and your database.The best way to think of these services would be as alternatives to store information via cookie.Storing session data in the cloud eliminates this problem!

Setting Up Redis

The folks over at Redis Labs offer a generous free tier for broke losers like you and me.They’re quite a reputable Redis host.Once set up, take note of the host, password, and port.

Installation Python’s Redis library.

$ pip install redis

Redis URIs

Like regular databases, we can connect to our Redis instance.

redis://:hostname.redislabs.com@mypassword:12345/0
[CONNECTION_METHOD]:[HOSTNAME]@[PASSWORD]:[PORT]/[DATABASE]

Create a Redis Client

Let’s connect to Redis by creating a client object using Redis :

import redis
from config import redis_uri

r = redis.StrictRedis(url=redis_uri)

There are two ways to instantiate Redis clients. Redis.Redis(), and redis.StrictRedis(). .

I highly recommend passing the keyword argument decode_responses=True, as this saves you the trouble of explicitly decoding.

...

r = redis.StrictRedis(
    url=redis_uri
    charset="utf-8",
    decode_responses=True
)

Birds-Eye View of Redis

Redis’ key/value store is a similar concept to Python dictionaries.Keys are always strings, but there are a few data types available to us.

r.set('ip_address', '0.0.0.0')
r.set('timestamp', int(time.time()))
r.set('user_agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3)')
r.set('last_page_visited', 'account')

Data Types in Redis

Values in Redis can be any of 5 data types:

STRING: Any value we create with r.set() is stored as a string type.

LIST: Redis lists are mutable arrays of strings in which they first appeared.

SET: A set is an unordered list of strings.Redis sets cannot contain duplicates.

ZSET (Sorted Set): Keeping sets in mind, a variation of the same data type .

HASH: Redis hashes are key/value pairs in themselves, allowing you to assign a collection of key/value pairs.

Data Expiration Dates

In our example where we stored some user-session information, let’s try setting a value with an expired date :

...

r.set('last_page_visited', 'account', 86400)

Add expiration to the last_page_visited key

This time around we pass a third value to r.set() which represents the number of seconds our pair will be stored.

When we use our HUD to refresh our table periodically, we can see this number counting down:

Working With Each Data Type

Strings

If strings contain integer values, there are many methods we can use to modify the string.

# Create string value
r.set('index', '1')
logger.info(f"index: {r.get('index')}")

# Increment string by 1
r.incr('index')
logger.info(f"index: {r.get('index')}")

# Decrement string by 1
r.decr('index')
logger.info(f"index: {r.get('index')}")

# Increment string by 3
r.incrby('index', 3)
logger.info(f"index: {r.get('index')}")

Redis string manipulation

index: 1
index: 2
index: 1
index: 4

Lists

Below we add items to a Redis list using a combination of .lpush() and .rpush(), as well as popping an item out using .lpop().

r.lpush('my_list', 'A')
logger.info(f"my_list: {r.lrange('my_list', 0, -1)}")

# Push second string to list from the right.
r.rpush('my_list', 'B')
logger.info(f"my_list: {r.lrange('my_list', 0, -1)}")

# Push third string to list from the right.
r.rpush('my_list', 'C')
logger.info(f"my_list: {r.lrange('my_list', 0, -1)}")

# Remove 1 instance from the list where the value equals 'C'.
r.lrem('my_list', 1, 'C')
logger.info(f"my_list: {r.lrange('my_list', 0, -1)}")

# Push a string to our list from the left.
r.lpush('my_list', 'C')
logger.info(f"my_list: {r.lrange('my_list', 0, -1)}")

# Pop first element of our list and move it to the back.
r.rpush('my_list', r.lpop('my_list'))
logger.info(f"my_list: {r.lrange('my_list', 0, -1)}")

Redis list manipulation

my_list: ['A']
my_list: ['A', 'B']
my_list: ['A', 'B', 'C']
my_list: ['A', 'B']
my_list: ['C', 'A', 'B']
my_list: ['A', 'B', 'C']

Sets

Redis sets are powerful party due to their ability .

# Add item to set 1
r.sadd('my_set_1', 'Y')
logger.info(f"my_set_1: {r.smembers('my_set_1')}'")

# Add item to set 1
r.sadd('my_set_1', 'X')
logger.info(f"my_set_1: {r.smembers('my_set_1')}'")

# Add item to set 2
r.sadd('my_set_2', 'X')
logger.info(f"my_set_2: {r.smembers('my_set_2')}'")

# Add item to set 2
r.sadd('my_set_2', 'Z')
logger.info(f"my_set2: {r.smembers('my_set_2')}'")

# Union set 1 and set 2
logger.info(f"Union: {r.sunion('my_set_1', 'my_set_2')}")

# Interset set 1 and set 2
logger.info(f"Intersect: {r.sinter('my_set_1', 'my_set_2')}")

Redis set manipulation

As expected, our union combines the set without duplicates, and the intersect finds values common to both sets :

my_set_1: {'Y'}'
my_set_1: {'X', 'Y'}'
my_set_2: {'X'}'
my_set2: {'X', 'Z'}'
Union: {'X', 'Z', 'Y'}
Intersect: {'X'}

Sorted Sets

Adding records to sorted sets using .zadd() .

# Initialize sorted set with 3 values
r.zadd('top_songs_set', {'Never Change - Jay Z': 1,
                         'Rich Girl - Hall & Oats': 2,
                         'The Prayer - Griz': 3})
logger.info(f"top_songs_set: {r.zrange('top_songs_set', 0, -1)}'")

# Add item to set with conflicting value
r.zadd('top_songs_set', {'Can\'t Figure it Out - Bishop Lamont': 3})
logger.info(f"top_songs_set: {r.zrange('top_songs_set', 0, -1)}'")

# Shift index of a value
r.zincrby('top_songs_set', 3, 'Never Change - Jay Z')
logger.info(f"top_songs_set: {r.zrange('top_songs_set', 0, -1)}'")

Redis sorted set manipulation

When attempting to insert a value at an index where one exists, the existing value (and those following) get pushed down to make room.

top_songs_set: ['Never Change - Jay Z',
        'Rich Girl - Hall & Oats',
                'The Prayer - Griz']
top_songs_set: ['Never Change - Jay Z',
        'Rich Girl - Hall & Oats',
        'Can\'t Figure it Out - Bishop Lamont',
        'The Prayer - Griz']
top_songs_set: ['Rich Girl - Hall & Oats',
        'Can\'t Figure it Out - Bishop Lamont',
        'The Prayer - Griz',
        'Never Change - Jay Z']

Hashes

record = {
    "name": "Hackers and Slackers",
    "description": "Mediocre tutorials",
    "website": "https://hackersandslackers.com/",
    "github": "https://github.com/hackersandslackers"
}
r.hmset('business', record)
logger.info(f"business: {r.hgetall('business')}")

Redis hash creation

business: {'name': 'Hackers and Slackers',
           'description': 'Mediocre tutorials',
           'website': 'https://hackersandslackers.com/',
       'github': 'https://github.com/hackersandslackers'}