Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
415 views56 pages

8 Data Modeling Patterns in Redis

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 56

E-Book

temperatures location
employees

products

courses instructors

courses_instructors

products courses instructors

8 Data Modeling Patterns


in Redis

© 2022 Redis
2

Table of Before you Get Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Summarizing The Patterns. . . . . . . . . . . . . . . . . . . . . . . . . 53

Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 The Embedded Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
By the end of the book you will have. . . . . . . . . . . . . . . . . . . 3 The Partial Embed Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
SQL versus NoSQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 The Aggregate Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Modeling 1-to-1 Relationships . . . . . . . . . . . . . . . . . . . . . . 11 The Polymorphic Pattern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
1-to-1 Relationships using SQL . . . . . . . . . . . . . . . . . . . . . . . . . 12 The Bucket Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
1-to-1 Relationships using Redis. . . . . . . . . . . . . . . . . . . . . . . . 13 The Revison Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
1-to-1 Relationships using Redis with the Partial Embed The Tree and Graph Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 The Schema Version Pattern . . . . . . . . . . . . . . . . . . . . . . . . . 55
Modeling Many-to-Many Relationships . . . . . . . . . . . . . 17 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Pattern 1: Relationship with Bounded Sides . . . . . . . . . . . 18
Pattern 2: Relationship with One Unbounded Side . . . . . 20
The Aggregate Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Without the Aggregate Pattern . . . . . . . . . . . . . . . . . . . . . . . 24
With the Aggregate Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . 26
The Polymorphic Pattern . . . . . . . . . . . . . . . . . . . . . . . . . 28
The Bucket Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Working with Time-series Data in Redis . . . . . . . . . . . . . . . 34
Aggregating with Time-series Data with Redis . . . . . . . . . 37
The Revision Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
The Tree and Graph Pattern. . . . . . . . . . . . . . . . . . . . . . . . 44
The Schema Version Pattern . . . . . . . . . . . . . . . . . . . . . . . 48

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


3

Before you Get


Started
Introduction
If you want to follow along with
When someone is looking to use NoSQL for an application, the question that most often comes up is, “How do I structure
some of the code examples
my data?” The short answer to this question is, as you might guess, it depends. There are several questions that can
used in this e-book, you can
inform how to structure your data in a NoSQL database.
clone the GitHub repository.
Is your application read heavy, or write heavy? What does the user experience of your application look like? How does
your data need to be presented to the user? How much data will you be storing? What performance considerations do
you need to account for? How do you anticipate scaling your application?

These questions are only a small subset of what you need to ask yourself when you start working with NoSQL. A common
misconception with NoSQL databases is that since they are “schemaless” you don’t need to worry about your schema.
In reality, your schema is incredibly important regardless of what database you choose. You also need to ensure that the
schema you choose will scale well with the database you plan to use.

In this e-book you will learn how to approach data modeling in NoSQL, specifically within the context of Redis. Redis is a
great database for demonstrating several NoSQL patterns and practices. Not only is Redis commonly used and loved by
developers, it also is a multi-model database. This means that while many of the patterns covered in this e-book apply to
different types of databases (e.g. document, graph, time series, etc.), with Redis you can apply all of the patterns in
a single database.

By the end of this, you should have


• A firm understanding of how to approach modeling data in Redis as well as in NoSQL generally.
• An understanding of several NoSQL data modeling patterns, their pros and cons, as well as use cases for
them in practice.
• A working knowledge of how to actually write code (with examples) to take advantage of NoSQL patterns
within Redis.

I’m sure you’re eager to get started, so let’s dive in!

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


4

SQL versus NoSQL


I’m sure at a certain level you understand the difference between SQL and NoSQL. SQL is a structured query language
whereas NoSQL can mean several different things depending on the context. However, generally speaking, the approach
to modeling data is fundamentally different in NoSQL than in SQL. There are also differences in terms of scalability, with
NoSQL being easier to scale horizontally.

When building applications you are probably using an object-oriented language like JavaScript, Java, C#, or others.
Your data is represented as strings, lists, sets, hashes, JSON, and so on. However, if you store data in a SQL database
or a document database, you need to squeeze and transform the data into several tables or collections. You also need
complex queries (such as SQL queries) to get the data out. This is called impedance mismatch and is the fundamental
reason why NoSQL exists.

A large application might use other systems for data storage such as Neo4J for graph data, MongoDB for document
data, InfluxDB for time series, etc. Using separate databases turns an impedance mismatch problem into a database
orchestration problem. You have to juggle multiple connections to different databases, as well as learn the different client
libraries used.

With Redis, in addition to the basic data structures such as strings, lists, sets, and hashes, you can also store advanced
data structures such as RedisJSON for documents, RediSearch for secondary indexing, RedisGraph for graph data,
RedisTimeSeries for time-series data, and RedisBloom for probabilistic data (think leaderboards).

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


5

This reduces impedance mismatch because your data is stored in one of 15 structures with little or no transformations.
You can also use a single connection (or connection pool) and client library to access your data. What you end up with is
a simplified architecture with purpose-built models that are blazing fast and simple to manage. For this reason, this e-book
will use Redis to explain several of the NoSQL data modeling patterns.

Most developers have at least a little understanding of SQL and how to model data in it. This is because SQL is widely
used and there are several incredible books and even full courses devoted to it. NoSQL is quickly growing and becoming
more popular. But given that when you’re talking about NoSQL you’re talking about more than just a document store, there
is a lot of ground to cover. That’s why when covering certain NoSQL data modeling patterns in this e-book, you will be
presented with what it might look like to model the data in SQL as well.

When you approach data modeling in SQL you are typically focused on relationships, as SQL is meant for set-based
operations on relational data. NoSQL doesn’t have this constraint and is more flexible in the way you model data.
However, this can lead to schemas that are overly complex. When considering NoSQL schema design, always think about
performance and try to keep things simple.

So to kick things off, let’s start by looking at something that is very near and dear to a SQL developer’s heart: relationships.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


6

Modeling 1-to1 Relationships


Imagine that you are creating a retail app that sells electronics. Let’s use Picture 1 and Picture 2 as an example of the
UI for a standard retail e-commerce app. First, you’ll create a list view of all the electronics and then a detailed view
that shows all the details of each item. There is a 1-to-1 relationship between each item in the list view and the detailed
view (shown in Picture 2) of the item. The detailed view shows all the details such as multiple photos, description,
manufacturer, dimensions, weight, and so on.

Picture 1 Picture 2

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


7

1-to-1 Relationships
using SQL Picture 3

In a relational database, you


may create a table called
products where each row holds
just enough data to display the
information in the list view.
Then, you may create another
table called product_details
where each row holds the
rest of the details. You would
also need a product_images
table, where you store all of
the images for a product. You
can see the entity relationship
diagram in Picture 3.

Picture 3 depicts the entity


relationships between
products, product_details,
and product_images and
represents a normalized Code Example 1
data model with a single
denormalized field image in SELECT
the products table. The reason p.id, p.name, p.image, p.price, pi.url
for this is to avoid having to use
a SQL JOIN when selecting FROM
the products for the list view. products p
Using this model, the SQL query
used to get the data needed
for the listview might resemble
Code Example 1.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


8

1-to-1 Relationships
using Redis
In Redis, similar to a relational Code Example 2
database, you can create a
collection called products
class ProductDetail(EmbeddedJsonModel):
and another called product_
description: str
details. But with RedisJSON
manufacturer: str
you can improve this by simply
dimensions: str
embedding product_images
weight: str
and product_details directly
images: List[str]
into the Products collection.
Then, when you query the
class Product(JsonModel):
Products collection, specify
name: str = Field(index=True)
which fields you need based
image: str = Field(index=True)
on which view you are trying
price: int = Field(index=True)
to create.
details: Optional[ProductDetail]
This will allow you to easily keep
all the data in one place. This is
called the Embedded Pattern
and is one of the most common
patterns you will see in NoSQL
document databases like
RedisJSON. Code Example  2
uses Python and a client library
called Redis OM (an ORM for
Redis) to model Products and
ProductDetails. Note that
ProductDetails is embedded
into Products directly, so all
of the data for a product will
be stored within the same
document.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


9

Code Example 2 also shows Code Example 3


how you can index fields using
Redis OM and RediSearch.
async def get_product_list():
Doing this turns Redis into not
results = await connections \
only a document store but
.get_redis_connection() \
also a search engine since
.execute_command(
RediSearch enables secondary
f’FT.SEARCH {Product.Meta.index_name} * LIMIT 0 10 RETURN 3 name image
indexing and searching. When
price’
you create models using
)
Redis OM, it will automatically
manage secondary indexes with
return Product.from_redis(results)
RediSearch on your behalf.

Using Redis OM we can write


a function to retrieve our
products list for the list view, as
shown in Code Example 3.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


10

Notice that in Code Example 3 Code Example 4


we are using the FT.SEARCH
(RediSearch) command, which
async def get_product_details(product_id: str):
specifies the index managed
return await Product.get(product_id)
on our behalf by Redis OM
and returns three fields: name,
image, and price. While the
documents all have details and
images embedded, we don’t
want to display them in the
list view so we don’t need to Picture 4
query them. When we want the
detailed view, we can query an
entire Product document. See
Code Example 4 for how to
query an entire document.

When using Redis, you can


use RedisInsight as a GUI tool
to visualize and interact with
the data in your database.
Picture 4 shows you what a
Products document looks like.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


11

Modeling 1-to-Many Relationships


Revisiting our electronics e-commerce store example, let’s talk about 1-to-many relationships. Let’s imagine in the
detailed view of a product you want to display a list of reviews for the product that show the reviewer name, rating,
publish_date, and comment. This is a 1-to-many relationship because one product can have multiple reviews and a
review can only relate to a single product.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


12

1-to-Many Using the entity relationship in Picture 5, you would need two SQL statements to get a product and its reviews. Code
Example 5 demonstrates what the SQL might look like. Your API would need to join the two queries together before
Relationships sending the data to the client.
using SQL
In a relational database, you Picture 5
would have a table called
products and another table
called product_reviews. Picture
5 shows the entity relationship
diagram for products and
product_reviews.

Code Example 5

SELECT
id, ‘name‘, ‘image‘, price
FROM
products
WHERE
id = 1;
SELECT
‘name‘ , rating, publish_date, comment
FROM
product_reviews
WHERE
product_id = 1;

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


13

1-to-Many Picture 6
Let’s say a product can have up to three videos. We
Relationships still have a 1-to-many relationship between products
using Redis and videos but since the number of videos is limited,
we can model this by embedding a list of video URLs,
shown in Picture 6, into our products collection.
In Redis, similar to a
relational database, you
could create two collections
called products, and
product_reviews exactly
like the entities above.
This strategy (having two Code Example 6
separate collections) works
Code Example 6 shows how you would embed a list
well for documents that are
class Product(JsonModel): of videos directly into your products collection using
unbounded and can keep
name: str = Field(index=True) RedisJSON and Redis OM for Python. When making a
growing.
image: str = Field(index=True) query, if you don’t want to show the videos, you can leave
price: int = Field(index=True) them out of your FT.SEARCH query (See Code Example 7).
For example, a product could
videos: Optional[List[str]]
have hundreds of thousands
of reviews, but it might only
have a few related videos.
Reviews in this case are Code Example 7
unbounded, but videos are
bounded. If you have a 1-to- async def get_products():
many relationship where the results = await connections \
“many” is limited to just a few .get_redis_connection() \
documents, then you can .execute_command(
simply embed it directly in f’FT.SEARCH {Product.Meta.index_name} * LIMIT 0 10 RETURN 3 name image
the parent document. price’
)

return Product.from_redis(results)

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


14

1-to-Many Picture 7

Relationships
using Redis with
the Partial Embed
Pattern
You can also combine these
techniques if it makes sense
for the application you are
building. For example, let’s say
even though your product
reviews are unbounded, you
want to quickly show the recent Code Example 8
reviews all the time. Instead of
doing two different queries,
class ProductDetail(JsonModel):
you can simply embed the
product_id: str = Field(index=True)
recent reviews directly into
reviewer: str
the parent document and still
rating: str
keep the rest of the reviews in a
published_date: datetime.date
different collection. This is called
comment: str
the partial embed pattern.
Picture 7 shows the entity
class Product(JsonModel):
relationship diagram for partially
name: str = Field(index=True)
embedding product_reviews.
image: str = Field(index=True)
price: int = Field(index=True)
Code Example 8 shows the
videos: Optional[List[str]]
data model for products with
recent_reviews: Optional[List[ProductReview]]
embedded recent reviews.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


15

Looking at Code Example 9 Code Example 9


you can see five functions. The
first function shows how to get
async def get_products():
a list of products with the name,
results = await connections \
image, and price fields. This is
.get_redis_connection() \
useful for the listview because
.execute_command(
you don’t need to show videos
f’FT.SEARCH {Product.Meta.index_name} * LIMIT 0 10 RETURN 3 name image
or recent reviews in your list of
price’
products. For the detailed view,
)
you do want to show product
videos and recent reviews. For
return Product.from_redis(results)
that, you can simply use the
get_product function above.
async def get_products(product_id: str):
return await Product.get (product_id)
This makes sense and enables
your UI to provide a glimpse
async def get_reviews(product_id: str):
of the reviews for a product.
return await = ProductReview.find (ProductReview.product_id == product_id)
Then, you might have a “See all
query.offset = 2
reviews” button in your UI which
return await query.all ()
triggers a call to get the rest
of the reviews. The get_reviews
async def add_review(review: ProductReview):
function in Code Example 9
product = await Product.get (review.product_id)
demonstrates how you can
review.recent_reviews.insert(0, review )
offset before querying
for reviews.
if (len(product.recent_reviews) > 2):
product.recent_reviews.pop()

await review.save()
await product.save()

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


16

The assumption in the code is Picture 8


that we only store the two most
recent reviews embedded in
each product document. Finally,
add_review shows how you
would insert a new review into
the product document. You
would simultaneously insert
it into your product_reviews
collection and pop off the
last review in your embedded
recent_reviews if necessary.

Picture 8 shows what the


data looks like in RedisInsight.
You can see one product and
five product_reviews in the
database, and also see that
there are two recent_reviews
embedded within the product,
and all videos embedded.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


17

Modeling Many-to-Many
Relationships
Many-to-Many relationships are very common and can be modeled in several ways with NoSQL databases. Here are
the two most common data modeling patterns for many-to-many relationships.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


18

Pattern 1:
Many-to-Many
Relationship with
Bounded Sides
Imagine you are creating an Picture 9
app for an online school that
has courses and instructors.
There is a many-to-many
relationship between courses
and instructors, but the list
of instructors who teach a
course is bounded on both
sides, meaning there will be a
limited number of instructors
teaching a course and a limited
number of courses taught by an
instructor.
Picture 10
In a relational database, you
might have a table called
courses and another table
called instructors. Then you
would have a junction table
called courses_instructors that
would store the relationship
between courses and
instructors. This can be seen in
Picture 9.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


19

In NoSQL, you can simplify Code Example 10


this by embedding a list
of instructor keys in each
class Product(JsonModel):
course document and a list of
name: str
course keys in each instructor
instructors: Optional[List[str]] = Field(index=True)
document. This can be seen
in Picture 10 and is known as
class Instructor(JsonModel):
two-way embedding. Let’s see
name: str = Field(index=True)
what this looks like in code.
courses: Optional[List[str]] = Field(index=True)

async def get_courses_with_instructor(instructor_pk: str):


return await Course.find(Course.instructors << instructor_pk).all()

async def get_instructors_with_course(course_pk: str):


return await Instructor.find(Instrusctor.courses << course_pk).all()

Code Example 10 uses Redis OM for Python to model will automatically create an index for the specified keys.
Courses with a name field and an instructors field The “get_courses_with_instructor” function takes in an
that is a list of strings representing the unique keys for instructor key and returns all of the courses that contain
instructors. There is also an Instructors collection with that instructor. The “get_instructors_with_course” does
a name field and a courses field that is a list of strings the opposite, returning instructors for a given course. The
representing the unique keys for the courses. Note the two-way embedding pattern works well when both sides
code, “Field(index=True)” is used to enable searching of the relationship are bounded. But what about when
for instructors and courses using RediSearch. Redis OM one side is unbounded?

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


20

Pattern 2:
Many-to-Many
Relationship with
One Unbounded
Side
Now consider the relationship Picture 11
between courses and students.
Let’s assume this is an online
school, and there could be any
number of students enrolled in
a course. This is an unbounded
many-to-many relationship
on the course side. However,
the student side is bounded
because a student will only
enroll in a limited number
of courses.

In a relational database, you


would still model this with a
junction table. However, in
NoSQL, it makes sense to
model it using an embedded
list on the bounded side of the
relationship. So you would store
a list of course keys in each
student document as shown
in Picture 11.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


21

Code Example 11

class Course(JsonModel):
name: str
instructors: Optional[List[str]] = Field(index=True)

class Student(JsonModel):
name: str = Field(index=True)
courses: Optional[List[str]] = Field(index=True)

async def get_students_in_course(course_pk: str):


return await Student.find(Student.courses << course_pk).all()

async def get_courses_for_student(student_pk: str):


student = await Student.get(student_pk)
return await Course.find(Course.pk << student.courses).all()

Code Example 11 shows Courses with the name and To recap, data modeling for many-to-many relationships
instructor fields. Since the number of students in a can be represented by embedding one or both sides
course is unbounded, you don’t need to store a list of of the relationship depending upon whether a side is
students in each course document. Instead, Students bounded or unbounded. If both sides are bounded, then
has a name field and a courses field that is a list of strings you can embed the relationship on both sides. If only
representing the unique keys for the courses in which a one side is bounded, then you should avoid embedding
student is enrolled. You also see two functions, one for the unbounded side. You should also favor embedding
finding students in a course and the other for finding references unless you have information that is primarily
courses that have a specific student enrolled. This is how static and won’t change over time.
you model many-to-many relationships when one side of
the relationship is unbounded and the other is bounded.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


22

The Aggregate Pattern


Let’s revisit the e-commerce site example. Every e-commerce site needs to keep a record of product reviews, and
also needs to show average ratings for each product on the page every time a customer looks at the page (shown in
Picture 12). This means, that for every page visit, the server needs to calculate the average rating for every product.
This could cause unnecessary overhead on the server and the database.

Picture 12

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


23

This is called the Aggregate Picture 13 When a new review is added you can increment the
Pattern, also known as the count and add the new rating to the existing ratings
Computed Pattern. In our sum. Then, when a customer searches for a product,
e-commerce example, you can you read the sum and the count of ratings and calculate
store the number of reviews the average on the front end by dividing the sum by the
as well as the sum of ratings count. This way every time a customer visits the page,
on each product’s JSON the server and the database just need to return the pre-
document. This can be seen in calculated values, resulting in improved performance.
Picture 13. Let’s look at a before and after using the Aggregate
Pattern in code.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


24

Without the Code Example 12

Aggregate Pattern class Product extends Entity {}


class ProductReview extends Entity {}
Code Example 12 shows how
you would model Products
const productSchema = new Schema(Product, {
and ProductReviews using
name: { type: ‘string’ },
Redis OM for Node.js. Redis
});
OM supports not only Python
const productReviewSchema = new Schema(ProductReview, {
and Node.js but also .NET and
productId: { type: ‘string’ },
Spring. The code shown in
author: { type: ‘string’ },
Code Example 12 is not using
rating: { type: ‘number’ },
the Aggregate   Pattern, so some
});
things are straightforward, and
others are more difficult.
async function addReview(productId, author, rating) {
const client = await getClient();
For example, to add a new
const productReviewRepo = client.fetchRepository(productReviewSchema);
review you simply create it
await productReviewRepo.createAndSave({
and save it to Redis. However,
productId,
getting a list of products is a
author,
little bit more complicated but
rating,
can still be done with Redis
});
OM using the FT.AGGREGATE
}
command. RediSearch
provides this command,
and it allows you to perform
aggregate queries easily.
In Code Example 12 the
FT.AGGREGATE command is
used to group product reviews
by productId and perform
a reduce to calculate the
average rating and count of
reviews.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


25

Then, after the command Code Example 12 cont’d


is run and the reviews are
returned, the average rating
async function getProducts() {
and number of reviews are
const client = await getClient();
extracted from the result
const productRepo = client.fetchRepository(productSchema);
and mapped onto the list of
const productEntities = await productRepo.search().return.all();
products before returning to
const results = await client.execute(
the client.
‘FT.AGGREGATE ProductReview:index * GROUPBY 1 @productId REDUCE AVG 1 @
rating REDUCE COUNT 0’.split(
The problem here is that while
/\s+/
Redis is incredibly fast, and for
)
many applications will be able
);
to handle this load relatively
quickly, it is suboptimal
const products = {};
especially for applications with
for (let result of results.slice(1)) {
a lot of user traffic. The code
const [, productId, , avgRating, , numReviews] = result;
in Code Example 12 in the
products[productId] = {
“getProducts” function would
avgRating: Number(avgRating),
have to be run every time a
numReviews: Number(numReviews),
customer visits the site. This
};
can lead to needless overhead.
}
Instead, let’s look at a better
approach using the Aggregate
return productEntities.map((entity) => {
Pattern.
return {
...entity.entityData,
...products[entity.entityId],
};
});
}

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


26

With the Code Example 13

Aggregate Pattern class Product extends Entity {}


class ProductReview extends Entity {}
Code Example 13 is very
similar to Code Example
const productSchema = new Schema(Product, {
12, only it takes advantage
name: { type: ‘string’ },
of the Aggregate Pattern
numReviews: { type: ‘number’ },
and has “numReviews”
sumRatings: { type: ‘number’ },
and “sumRatings” fields on
});
Products. The “addReview”
const productReviewSchema = new Schema(ProductReview, {
function now requires you
productId: { type: ‘string’ },
to increment “numReviews”
author: { type: ‘string’ },
and add the rating to the
rating: { type: ‘number’ },
“sumRatings” field.
});

async function addReview(productId, author, rating) {


const client = await getClient();
const productRepo = client.fetchRepository(productSchema);
const productReviewRepo = client.fetchRepository(productReviewSchema);
const productEntity = await productRepo.fetch(productId);

productEntity.entityData.numReviews += 1;
productEntity.entityData.sumRatings += rating;

return Promise.all([
productRepo.save(productEntity),
productReviewRepo.createAndSave({
productId,
author,
rating,

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


27

However, the “getProducts” Code Example 13 cont’d


function is much simpler.
Remember that a typical
}),
e-commerce application will
]);
typically have a much larger
}
number of reads than writes,
so you want to optimize your
async function getProducts() {
data for reads. When building
const client = await getClient();
read-heavy applications,
const productRepo = client.fetchRepository(productSchema);
consider using the Aggregate
Pattern to reduce the amount
return productRepo.search().return.all();
of computation necessary
}
at read time for aggregate
information.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


28

The Polymorphic Pattern


Polymorphism is used when you have things that have some similarities and also some differences. Consider a
catalog of products where each product has a name, brand, sku, and model but some products of a certain type
might contain size and color while others might not.

For example, a game console and a pair of earbuds might have similar fields such as the product name, brand, model
number, sku, and reviews. However the game console has some unique properties such as storage type, number of
HDMI ports, etc. The pair of earbuds also has unique fields such as battery life, connection type, fit, etc.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


29

Picture 14 shows the entity Picture 14


relationship diagram you might
use in SQL. In SQL you might
store some of the shared fields
in a “products” table, and then
have separate tables to store
specifics about the different
types of products. Now to get all
the products you need to join all
these tables.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


30

An example query might look Code Example 14


like Code Example 14. This can
get unwieldy as you add many
SELECT
different types of products to
p.id, p.name, p.brand, p.sku, p.model,
your catalog.
a.storage_type,
gc.storage_type, gc.hdmi_ports, gc.gpu,
You could also choose to
e.storage_type, e.usb_ports, e.hdmi_ports,
store any possible field in the
ph.storage_type, ph.ports, ph.battery
products table, but this can
FROM
also get unwieldy, could lead
products p
to issues if your database limits
INNER JOIN
the number of columns you can
appliances a
store, and requires you to have a
ON a.product_id = p.id
ton of nullable fields in each row.
INNER JOIN
game_consoles gc
ON gc.product_id = p.id
INNER JOIN
electronics e
ON e.product_id = p.id
INNER JOIN
phones p
ON ph.product_id = p.id
WHERE
p.id = 1;

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


31

In Redis, because you get Code Example 15


a flexible schema, you can
simply store all these in a single
class Product extends Entity {}
collection without having to
worry about having a ton of null
const productSchema = new Schema(Product, {
fields. Further, to distinguish
type: { type: ‘string’ },
between different product
name: { type: ‘string’ },
types, you can have a “type”
brand: { type: ‘string’ },
field that groups them together.
sku: { type: ‘string’ },
For example, in our case, we
model: { type: ‘string’ },
can have type = “game console”
batteryLife: { type: ‘string’ },
and type = “earbuds”.
connectionType: { type: ‘string’ },
fit: { type: ‘string’ },
Let’s look at what this looks like
usbPorts: { type: ‘number’ },
in code.
hdmiPorts: { type: ‘number’ },
storageType: { type: ‘string’ },
});

async function getProducts() {


Code Example 15 shows how
const client = await getClient();
using the Polymorphic Pattern
const productRepo = client.fetchRepository(productSchema);
in Redis makes it really easy
to work with products. The
return productRepo.search().return.all();
“getProducts” function can get
}
all products by looking at a
single collection. If you need to
async function getProductByType(type) {
get products of a certain type,
const client = await getClient();
like game consoles, you can
const productRepo = client.fetchRepository(productSchema);
modify your where clause to
search by type.
return productRepo.search().where(‘type’).equals(type).return.all();
}

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


32

Picture 15 shows what the data Picture 15


might look like in RedisInsight.
Something to note is that
given Redis allows for a flexible
schema you only see the
common fields as well as fields
specific to a product type. In
Picture 15 you see fields that
relate to the earbuds product
type, but not fields for any
other type.

When building applications,


think about how to best design
your data schema using
fewer collections, and take
advantage of the Polymorphic
Pattern to combine similar
types of data.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


33

The Bucket Pattern


Imagine you are building an application that will take temperature measurements for monitoring purposes. You want
to use Redis for this because it is fast, and you will need to access the data frequently. You may think to store each
measurement embedded in a JSON document with the timestamp and temperature reading (shown in Picture 16).
However, while this approach seems reasonable, it can cause issues as your application scales to have tons of these
measurements.

Picture 16

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


34

Working with Code Example 16

Time-series Data async function createTimeSeries() {


in Redis const client = await getClient();
const exists = await client.execute(‘EXISTS temperature:raw’.split(‘ ‘));
A better way to store this
is to use the time-series if (exists === 1) {
capabilities of Redis. With return;
RedisTimeSeries you can store }
your measurements in a time-
series data structure. In this const commands = [
case you might also want to ‘TS.CREATE temperature:raw DUPLICATE_POLICY LAST’,
have easy access to the average ];
temperature over a period of
time. Let’s see what this looks for (let command of commands) {
like in code: await client.execute(command.split(‘ ‘));
}
}
Using Code Example 16 as a
guide, you need to first create a
time series before you can add
measurements to it. However,
you want to make sure the time
series doesn’t already exist
before you create it using the
EXISTS command in Redis. To
create a new time series, you
need to use the TS.CREATE
command.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


35

We are calling our time series Code Example 16 cont’d


“temperature:raw” because
it will be storing all of the raw
async function add(values) {
temperature measurements
const client = await getClient();
from our data. We are also
const chunkSize = 10000;
specifying a DUPLICATE_POLICY
of “last”, meaning that if we try
for (let i = 0; i < values.length; i += chunkSize) {
to add multiple samples with the
const chunk = values.slice(i, i + chunkSize);
same timestamp it will always
const series = chunk.reduce((arr, value) => {
keep the newest reported value.
return arr.concat([
‘temperature:raw’,
new Date(value.date).getTime(),
value.temp,
]);
}, []);

// TS.MADD temperature:raw timestamp temp temperature:raw timestamp temp ...


await client.execute([‘TS.MADD’, ...series]);
}
}

TS.REVRANGE temperature:raw 0 + COUNT 14400

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


36

You will also see an add function Picture 17


here that takes in an array of
temperature readings with the
timestamp and temperature
value. The sample data has a
year’s worth of temperature
readings every 6 seconds,
totaling about 5.3 million
samples. For this reason, we are
splitting the data into chunks
of 10 thousand samples. We
are then using the TS.MADD
command to store each batch
of 10 thousand samples.

You can use TS.MADD to


append new values to one
or more time series. In this
case, I am appending to the
temperature:raw time series.
If you want to visualize a
time series, RedisInsight is a
great tool. You can use the
workbench and run a TS.RANGE
or TS.REVRANGE command
to get a graph of your time-
series data. For example, the
following command would give
us the prior day’s temperature
readings, and Picture 17
shows the visualization from
RedisInsight.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


37

Aggregating With the Bucket Pattern and Redis, you can automatically aggregate your data as you go along. Say, for example,
you want to keep track of the average hourly temperature reading. Redis can do this automatically for you with the
Time-series Data TS.CREATERULE command. Let’s see what this looks like in code.
with Redis Code Example 17 shows two new time series added, temperature:daily and temperature:monthly. It also shows two rules
created using TS.CREATERULE to take the time-weighted average temperatures as they are added to temperature:raw
While it is nice to get a view of and store them in the respective daily and monthly time series.
all the data, what is also nice is
to be able to see the average
temperature over a period of Code Example 17
time. You can do this using
the TS.RANGE command and
async function createTimeSeries() {
specifying an AGGREGATE
const client = await getClient();
command of twa, for time-
const exists = await client.execute(‘EXISTS temperature:raw’.split(‘ ‘));
weighted average, as well as a
bucket duration. Let’s specify a
if (exists === 1) {
bucket duration of the number
return;
of milliseconds in a month so
}
we can see the average monthly
temperature in our time series.
const commands = [
‘TS.CREATE temperature:raw DUPLICATE_POLICY LAST’,
You can use the TS.RANGE
‘TS.CREATE temperature:daily DUPLICATE_POLICY LAST’,
command to get the average
‘TS.CREATE temperature:monthly DUPLICATE_POLICY LAST’,
temperature over a period of
‘TS.CREATERULE temperature:raw temperature:daily AGGREGATION twa 86400000’,
time. However, as you store
‘TS.CREATERULE temperature:raw temperature:monthly AGGREGATION twa
more measurements the time it
2629800000’,
takes to calculate the average
];
will increase. There is a better
way to handle this using the
for (let command of commands) {
Bucket pattern.
await client.execute(command.split(‘ ‘));
}
}

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


38

The TS.CREATERULE command takes in a sourceKey, destinationKey, aggregator function, and bucketDuration. The
sourceKey is the key to the source time series where you are storing your raw data. The destinationKey is where you want
to store the new, bucketed time series. The aggregator is the function you want to use for your buckets. In our case, we
will use twa to store the time-weighted average. Finally, the bucketDuration is the timespan in milliseconds for your buck-
ets.

Note that you should never explicitly add to the bucketed time series as it will be done automatically for you. Also, the rule
does not retroactively apply to an existing time series. Only new samples that are added to the source time series will be
aggregated. So if you look at the differences between Code Example 16 and Code Example 17 you will see that we only
needed to add the two new time-series keys and the two rules. Redis takes care of the rest!

Now if we want to get the average monthly temperature we can simply query the monthly time series with the following
TS.RANGE command.

TS.RANGE temperature:monthly 0

Note that you don’t have to specify any aggregator function because it’s already done for you using TS.CREATERULE. That
not only makes the command more readable than the aggregate command we had to run previously, but it also runs
much faster.

While Redis is incredibly fast when performing aggregate queries, using the bucket pattern to keep track of aggregate
values as you go is much faster.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


39

The Revision Pattern


Imagine you’re an editor for a digital publication. You work with several team members to write and edit each post
before it gets published. You need to keep track of content revisions as well as who made those revisions.

As seen in Picture 18, in SQL you might store all posts in a table and have the revisions in a separate table. Then,
when you want to view the revisions for a specific post you need to query the latest version from the posts table and
join all the revisions from the revisions table that match that post.

Picture 18

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


40

With Redis, you can store Code Example 18


a post and its revisions in
a single document. This
simplifies your queries and class Revision(EmbeddedJsonModel):
lets you organize your content title: str = Field(index=True)
more logically. This is called body: str = Field(index=True)
the Revision Pattern. Let’s see author: str = Field(index=True)
what this looks like in code. last_saved_by: str = Field(index=True)
created_at: datetime.date = Field(index=True)
updated_at: datetime.date = Field(index=True)

Code Example 18 shows class Post(JsonModel):


how you would model Posts title: str = Field(index=True)
and embedded Revisions body: str = Field(index=True)
using Redis OM for Python. author: str = Field(index=True)
Both models share attributes last_saved_by: Optional[str] = Field(index=True)
such as title, body, author, created_at: Optional[datetime.date] = Field(index=True)
last_saved_by, created_at, and updated_at: datetime.date = Field(index=True)
updated_at. Posts have an revisions: Optional[List[Revision]]
additional attribute which is
the list of revisions.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


41

Code Example 19 shows the Code Example 19


standard CRUD operations
for a post. To create a new
post we simply take in all the async def create_post(**args):
post attributes and save them dt = datetime.now().isoformat()
to Redis using Redis OM. To post = Post(
update a post, we first get title=args[“title”],
the post from Redis, create a body=args[“body”],
new revision for it, insert it at author=args[“author”],
the beginning of the post’s last_saved_by=args[“last_saved_by”],
revisions list, update the post created_at=dt,
with the new attributes, then updated_at=dt,
save the post. revisions=[]
)

return await post.save()

async def update_post(id: str, **args):


post = await Post.get(id)
revision = Revision(
title=post.title,
body=post.body,
author=post.author,
last_saved_by=post.last_saved_by,
created_at=post.created_at,
updated_at=post.updated_at)

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


42

When you get a list of posts, Code Example 19 cont’d


you don’t always want the
revisions for each post. The
FT.SEARCH command lets you post.revisions.insert(0, revision)
search Redis using an index post.title = args.get(“title”, post.title)
and also specify the fields to post.body = args.get(“body”, post.body)
return. Redis OM automatically post.author = args.get(“author”, post.author)
creates the Post index for post.last_saved_by = args.get(“last_saved_by”, post.last_saved_by)
you, and then you can use it post.updated_at = datetime.now().isoformat()
to run custom searches if you
need to. In “get_posts”, we are return await post.save()
querying all posts, denoted
by the asterisk, and returning async def get_posts():
5 fields: title, body, author, results = await connections \
created_at, and updated_at. .get_redis_connection() \
Now let’s see what this looks .execute_command(
like in RedisInsight. f’FT.SEARCH {Post.Meta.index_name} * LIMIT 0 10 RETURN 5 title body
author created_at updated_at’
)

return Post.from_redis(results)

async def get_post(id: str):


return await Post.get(id)

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


43

Using Picture 19, in Picture 19


RedisInsight you can see there
are two posts in the database.
For the selected post there are
some revisions. Note the title,
body, author, and last_saved_
by fields are different in the
main post than in the revisions.

While publishing is a very


common industry that uses
the Revision Pattern it is also
applicable to industries where
you need an audit trail of all
document changes such as
law, finance, healthcare, and
insurance.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


44

The Tree and Graph Pattern


When working with NoSQL document databases it is generally recommended to minimize the number of JOINs
you need to build your data model. Even in SQL, JOINs can cause overhead and slow down data retrieval. However,
sometimes your data requirements are such that you cannot avoid JOINs. We’ve already covered various patterns for
modeling relatively simple relationships, including when to embed and when to keep things separate. One thing we
didn’t talk about is more complex relationships such as graphs or trees.

For example, imagine you are building an enterprise resource planning (ERP) system. One of the most important parts
of the system is the org chart. At the very least, you need to be able to show details about each employee, where
they are located, and who they report to (or who reports to them). The most logical way to store this data is in a tree.
Let’s look at how you might do this in traditional SQL as well as NoSQL with Redis using the Tree and Graph Pattern.

Storing trees in SQL is straightforward, as SQL is designed specifically for relationship modeling. To model your org
chart, you might have two tables (shown in Picture 20): employees and locations.

Picture 20

Using Picture 20 let’s look at two potential SQL queries you might want to make. You need one query for getting
employees who work at a specific location and another query for getting employees who work for a specific
manager.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


45

Code Example 20 shows Code Example 20


two relatively simple queries
for getting employees with
a specific manager or at a # Get employees with a specific manager
specific location. This works SELECT
well, but what if you want to e.id, e.name, e.title
go one or more levels deeper FROM
with the manager query. For employees e
example, if you want to find INNER JOIN
employees who have two employees e2
degrees of separation from ON e2.reportsto_id = e.id
someone. This becomes more WHERE
complicated to accomplish e.id = 1;
with SQL and requires you
to add additional JOINs. # Get employees who work at a specific location
Depending on the complexity SELECT
of your query and how many e.id, e.name, e.title
employees you have in your FROM
database, it can become employees e
prohibitively slow to use SQL to INNER JOIN
get the information you need. locations l
ON l.id = e.location_id
WHERE
l.id = 1;

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


46

Redis comes with built-in Code Example 21


graph capabilities, which
makes working with complex
relationships more intuitive and # Get employees with a specific manager
faster. Under the hood, Redis GRAPH.QUERY Org “MATCH (e:Employee)-[:REPORTS_TO]->(m:Employee { name: ‘Doug’})
uses the Cypher language to RETURN e,m”
allow you to work with trees
and graphs. Let’s take the same # Get employees who work at a specific location
example from Picture 20 and GRAPH.QUERY Org “MATCH (e:Employee)-[:WORKS_AT]->(l:Location { name: ‘Seattle’})
see how you would use Redis to RETURN e,l”
accomplish the same thing.

Code Example 21 is the query Picture 21


you would perform on Redis
to get results that match
those from Code Example
20, and Picture 21 shows the
visualization you get when
you run the first query in the
RedisInsight Workbench.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


47

Code Example 22 shows how, Code Example 22


with a small addition of “*2”,
you can get employees with
two degrees of separation from # Get employees two degrees separated from a specific manager
Doug. GRAPH.QUERY Org “MATCH (e:Employee)-[:REPORTS_TO*2]->(m:Employee { name: ‘Doug’})
RETURN e,m”
The built-in graph capabilities
of Redis allow you to use the
Tree and Graph Pattern to
model complex relationships
without having to worry about
the complexity of SQL queries.
While this pattern is useful for
HR systems, it is also seen in
Content Management Systems
(CMSs), product catalogs,
social networks, and more.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


48

The Schema Version Pattern


In the past, you’ve probably built an app, designed your data model and everything seemed perfect. Then something
comes up that prompts a change to your data model. You need to determine whether you should make a breaking
change and rewrite all your application code to use the new data model at the same time.

For example, when your app started maybe you were storing one email address per user but now you need to store
multiple email addresses. You could add additional columns such as “email2”, “email3”, etc. However, a better way is
to use a list of email addresses.

While the most future-proof way is to use an embedded list, the problem is all your existing users are stored with
email address fields directly in their document rather than in the “email addresses” list. In addition, all of your existing
code is using the email address fields on a user, not from within the email address list. So what do you do? This is
where you can use the Schema Version pattern to your advantage.

The Schema Version pattern is a way of assigning a version to your data model. This is usually done at the document
level, but you may also choose to version all of your data as part of an API version. It is recommended that you
always assign a version to your documents so that you can change them in the future without having to worry about
immediately migrating all of your data and code. If you’re using Redis your schema is flexible, and you can make
changes to your existing schema without any downtime.

If you aren’t already using the Schema Version Pattern the good news is you can start today without making any
significant changes to your application logic. Let’s dive into some code to see how you might introduce the Schema
Version Pattern into an existing system.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


49

In Code Example 23 we are Code Example 23


using Redis OM for Node.js and
defining a User schema with
name and email. To create a class User extends Entity {}
new user we need to save the
user data to Redis, then return const userSchema = new Schema(User, {
the new user. To get a user we name: { type: ‘string’ },
simply fetch it from Redis using email: { type: ‘string’ },
its unique ID. Finally, to update });
a user we fetch it by ID and
then update the fields. In a full export async function create(data) {
production app, you might do const client = await getClient();
a lot more than this, but this is const repo = client.fetchRepository(userSchema);
a simple example of how you const user = repo.createEntity(data);
might start writing an app to
create, read, and update users. await repo.save(user);

return user.toJSON();
}

export async function read(id) {


const client = await getClient();
const repo = client.fetchRepository(userSchema);
const user = await repo.fetch(id);

return user.toJSON();
}

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


50

Code Example 23 cont’d

export async function update(id, data) {


const client = await getClient();
const repo = client.fetchRepository(userSchema);
const user = await repo.fetch(id);

user.name = data.name;
user.email = data.email;

await repo.save(user);

return user.toJSON();

This is working well for now, but what happens when we The best way to do this is to create a translation function
need to change users to have a list of email addresses? that you run whenever a new user is created or an old
We need to change the existing schema and write some user is updated. The reason you want to do this is twofold.
code to incrementally migrate old users to the new First, you only want to migrate a user document one
schema. time, so you don’t want to translate it during read time.
Second, you want to allow your existing applications to
continue to use older schemas. Let’s see how we might
incrementally migrate old documents to use the new
schema while supporting existing application logic.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


51

Using Code Example 24, Code Example 24


this time we’re defining our
User schema with additional
fields for schema, contact, class User extends Entity {}
and an emails list. We want to
eventually rename the email const userSchema = new Schema(User, {
field to contact so it is more schema: { type: ‘string’ },
straightforward. However, we name: { type: ‘string’ },
don’t want to remove the email email: { type: ‘string’ },
field yet because we still want contact: { type: ‘string’ },
to support legacy code. emails: { type: ‘string[]’ },
});

The “translate” function will function translate(data, schema = ‘1’) {


translate data from the old // Ignore data if using the old schema
schema to the new schema. By if (schema === ‘1’) {
default, we will assume anyone return data;
using the function is still using }
the old schema. This is safe as
you don’t want to automatically // Ignore data if already using schema ‘2’
assume everyone wants to if (schema === ‘2’ && data.schema === ‘2’) {
start using the new schema. In return data;
the translate function, we do }
nothing if the old schema is in
use. We also do nothing if the // Migrate old data to new schema
schema of the incoming data data.schema = schema;
matches. If neither of those data.emails = [data.email];
conditions is true, we migrate data.contact = data.email;
the old schema to the new data.email = null;
format and return it.
return data;
}

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


52

We also need to rewrite the Code Example 24 cont’d


create function to support
both schemas. Once again we
use the old schema by default. export async function update(id, data, schema = ‘1’) {
Then, we call to translate the const client = await getClient();
data, save it to Redis, and const repo = client.fetchRepository(userSchema);
return the JSON. Updating an const user = await repo.fetch(id);
existing user can be a little bit
tricky. First we fetch the user data = translate(data, schema);
from Redis, then make a call
to translate the incoming data. if (schema === ‘1’) {
Finally, we update the user user.schema = schema;
fields according to the schema user.name = data.name;
in use. user.email = data.email;
} else {
While this might be a contrived user.schema = data.schema;
example, the principles user.name = data.name;
still apply in a real-world user.email = data.email;
application. You should always user.contact = data.contact;
use the Schema Version user.emails = data.emails;
pattern to aid in incrementally }
migrating your data as you
require changes to your data await repo.save(user);
model.
return user.toJSON();

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


53

Summarizing The Patterns


That was a lot of information to take in! Let’s briefly look back at all the patterns we learned throughout this e-book,
and also consider the use-cases for each one.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


54

The Embedded Pattern The Aggregate Pattern


The Embedded Pattern is used in NoSQL document The Aggregate Pattern is used to store attributes of
databases, such as Redis, to allow you to keep all a larger embedded (or separate) collection within a
information relevant to a specific data type within document. We used it to store the number of reviews and
the same document. This is useful in a wide range of sum of ratings in our product documents, thus making
applications. Almost every application you build can and it easier to calculate the average rating for products in
should take advantage of The Embedded Pattern. a listview. However, this pattern is useful in IoT, real-time
analytics, and other types of catalogs as well.

The Partial Embed Pattern The Polymorphic Pattern


The Partial Embed Pattern is where you embed a subset The Polymorphic Pattern applies when there are several
of a larger collection within a document. This is useful in different variations of similar data, with more similarities
e-commerce applications to store product reviews. It is than differences. It’s useful for when you really want data
also useful in publishing and social media applications kept in a single collection for viewing purposes. We used
to show top comments. There are many use-cases for the example of a product catalog, where products have
the partial embed pattern, the important thing is for you some shared properties and other unique properties
to recognize when it might be useful based on how you based on their type.
have to present information to your users.
However, we wanted to be able to query and show
all products as easily as possible. The Polymorphic
Pattern lets us store all of the permutations of products
in a single product collection. This pattern is also very
useful in content management systems (CMS), learning
management systems (LMS), and customer relationship
management systems (CRM).

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


55

The Bucket Pattern The Tree and Graph Pattern


You might also call this pattern the time-series pattern. The Tree and Graph Pattern is useful when you need
The Bucket Pattern is where you have time-series data to model complex relationships and you can’t take
and you want to store it in aggregate “buckets” based on advantage of the Embedded Pattern. This typically means
time periods. This is useful when managing streaming your data is hierarchical and needs to be accessed and
data such as sensor readings, real-time analytics, and IoT changed frequently. The key advantage NoSQL has over
applications. Redis makes this pattern incredibly easy with SQL here is avoiding multiple JOINs for accessing several
its built-in time-series bucketing capabilities. levels of a tree. This pattern can be seen in ERPs, CMSs,
product catalogs, and social networks.

The Revision Pattern The Schema Version Pattern


Use the Revision Pattern when you need to maintain Last, but certainly not least, the Schema Version Pattern
previous versions of a document. We used a CMS applies to every application you ever build using NoSQL.
example, but this pattern is useful in the legal, financial, It is incredibly useful for helping you improve your
healthcare, and insurance industries. NoSQL document schema over time. Redis and other NoSQL databases are
databases like Redis make it easy to apply this pattern sometimes referred to as “schemaless.” While this is true,
because you can embed revisions with the latest version in reality, a schema is very important in every database.
all within the same document.
You also need to be able to change your data model and
let applications that use your data upgrade gracefully. The
Schema Version Pattern enables you to let applications
upgrade when they want, and understand a document
based on its schema version.

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis


56

Conclusion
I hope you enjoyed this e-book. Let it serve as a reference for you as you go out and build amazing applications
using NoSQL and Redis! Remember, even though all of the examples in this e-book use Redis, the same patterns and
principles apply to other NoSQL databases.

Also, keep in mind that all of the patterns mentioned can be used together in your application. When you are
approaching building an application, have these patterns in the back of your mind, and figure out which pattern applies
best to the problem you are trying to solve. You have been given the tools and knowledge needed to build applications
using NoSQL. Now you just need to get started!

Redis E-Book / 8 Data Modeling Patterns in Redis © 2022 Redis

You might also like