Design Patterns using Redis
Share:
Redis is a flexible data structure server commonly used as a database, cache and message broker. It supports multiple types of data structures like strings, hashes, lists, sets, sorted sets with range queries, bitmaps and more. The various data types offered by Redis enables us to leverage them in specific ways to build powerful applications. In this tutorial, we will explore common Redis design patterns and use the context of a movie application for illustration.
Scenario: Streaming service (like Netflix)
Consider that we are architecting a streaming service that hosts numerous movies, series, and other content. We will explore how Redis can be used in different scenarios that occur within this service by using effective design patterns.
Caching:
A common use case for Redis is as a cache, providing fast access to data that is expensive to fetch or compute. Suppose in our movie application, fetching movie details from the database is a resource-intensive task and causes a substantial delay. In such a scenario, we can use Redis to cache the movie details.
import redis
r = redis.Redis(host='localhost', port=6379)
MOVIE_KEY = 'movie:{}' # key format for movie details
DATABASE = 'movie_database'
def get_movie(movie_id):
movie = r.get(MOVIE_KEY.format(movie_id)) # attempt to get movie from Redis
if movie is None: # movie details not in cache
movie = fetch_movie(DATABASE, movie_id) # fetch details from DB
r.set(MOVIE_KEY.format(movie_id), movie) # store movie details in Redis
return movie
Here, we first try to get the movie details from Redis. If it fails, we fetch them from our hypothetical database and subsequently store them in Redis for future use.
Leaderboard:
Suppose we want to maintain a Top 10 movies leaderboard, which keeps a real-time track of the highest-rated movies. Redis Sorted Sets allow us to keep our leaderboard ordered and efficient, even as scores are updated in real-time. The movie rating is the score that Redis uses to order the set.
LEADERBOARD_KEY = 'movie_leaderboard'
def vote(movie_id, vote_total):
r.zadd(LEADERBOARD_KEY, {movie_id: vote_total})
def top_movies(limit=10):
return r.zrevrange(LEADERBOARD_KEY, 0, limit-1, withscores=True)
In the above code, we add movies to the leaderboard using the zadd
command and fetch the top rated movies using the zrevrange
command.
Rate Limiting:
Imagine if there was a scenario where you want to restrict the number of API requests a user can make in a specific timeframe, for instance, to prevent DDoS attacks or web scraping. Redis can be used to implement an efficient rate limiter using the INCR
command and expire functionality. Here's how:
def is_allowed(user_id, block_time, limit):
user_key = f"user:{user_id}"
current_requests = r.incr(user_key)
if current_requests == 1:
r.expire(user_key, block_time)
return current_requests <= limit
Every user request increases the counter for a specific user id, and when the counter is 1, a block time (expiring time) is added to it (all subsequent requests share this expiration time). This function will permit requests as long as the user has not exceeded the limit within the block time.
Pub/Sub:
Redis has a feature called "Pub/Sub” (Publish/Subscribe), which is excellent for real-time notifications. Suppose our movie application wants to notify users whenever a new movie is added to their preferred genre. We can implement this using Redis' Pub/Sub mechanism.
def add_movie_genre_publisher(movie):
r.publish('new_movie', movie)
def new_movie_genre_subscriber():
sub = r.pubsub()
sub.subscribe('new_movie')
while True:
message = sub.get_message()
if message and not message['data'] == 1:
print('New Movie Added:', message['data'])
In this example, whenever a movie is added, it's published to the 'new_movie' channel. Any subscribers to this channel receive a notification when a new message (movie) is published.
Social Network Features:
Redis also excels at modeling and managing many types of social network features. Let's take the example of users following other users (like Twitter). This can be achieved using Redis sets.
def follow_user(user_id, followed_user_id):
r.sadd(f"user:{user_id}:following", followed_user_id)
r.sadd(f"user:{followed_user_id}:followers", user_id)
def unfollow_user(user_id, followed_user_id):
r.srem(f"user:{user_id}:following", followed_user_id)
r.srem(f"user:{followed_user_id}:followers", user_id)
def get_followers(user_id):
return r.smembers(f"user:{user_id}:followers")
In this code, users can follow/unfollow others, and we can fetch a list of followers for a user.
Conclusion:
Redis is an incredibly versatile database and can be finetuned to solve different problems, thanks to its unique data structures. It's imperative to choose the right Redis data types according to your use case for efficient design. It allows you to implement creative solutions and utilize the many powerful features it offers, creating robust and highly responsive applications.
Whether it's building a streaming service like in our example or any other application, Redis simplifies many complex tasks with its straightforward design patterns, enhancing your projects with faster operations and real-time capabilities.
0 Comment
Sign up or Log in to leave a comment