MongoDB Data Modeling
Steps to take
Identify workload
Map relationship
Apply design patterns
Optimization - creating indexes
Identify workload
At this step, we mostly need to identify if our application is read or write-heavy and which resources are critical.
Example
| Action | Type | Information | Frequency | Priority |
| Submit a new article | Write | author, text | 10 per day | High |
| Submit a comment on an article | Write | user, text | 1,000 per day (100 per article) | Medium |
| View an article | Read | article id, text, comments | 1,000,000 per day | High |
| View article analytics | Read | article id, comments, clicks | 10 per hour | Low |
Data relationship
Referencing or Embedding
Example of referencing
// Referencing
// books
{
"_id": "book1",
"title": "MongoDB",
"authors": ["author1", "author2"]
}
// authors
[
{ "author_id": "author1", "name": "Huy" },
{ "author_id": "author2", "name": "Steve" }
]
Example of embedding
{
"_id": "book1",
"title": "MongoDB",
"authors": [
{ "author_id": "author1", "name": "Huy" },
{ "author_id": "author2", "name": "Steve" }
]
}
When to use which?
In MongoDB, the recommended way to handle related data is to embed it into a sub-document - retrieve information without $lookup. The other way is to use references.
๐ The golden rule: Data that is accessed together should be stored together
Letโs take a look at the above books and authors relationship
Here are the guidelines
Simplicity - would keeping the pieces together lead to a simpler data model and code? => Yes - Embed
Go Together - Do the pieces of information have a โhas-aโ, โcontainsโ relationship? Yes - Embed
Query Automicity - Does the app query the pieces of info together? Yes - Embed
Update Complexity - Are the pieces of info updated together? No - Reference
- We can update the book and author separately
Archival - should the pieces of information be archived at the same time? No - Reference
Cardinality * - is there a high growth in the child side of the relationship? No - Embed
Data Duplication - Would data duplication be too complicated to manage and undesired? No - Embed
Document Size - Would the combined size take too much memory or transfer bandwidth? No - Embed
Document Growth - would the embedded piece grow without bound? No - Embed
Workload - are the pieces of info written at different times in a write-heavy workload? No - Embed
- If we frequently update related data, then better to use a reference
Individuality - for the child side, can the pieces exist by themselves without a parent? No - Embed
- An author cannot exist in our app without a book
๐ But mostly consider
Array size
Update frequency of embedded data
Whether the embedded entity is often queried independently
One-to-one
Example: one company has one headquarters
Embedded - recommended in this case โ
{
"_id": "publisher123",
"name": "MongoDB Press",
"street": "123 Data Lane",
"city": "San Francisco",
"state": "CA",
"country": "USA",
"zip": "94107"
}
// it's better to group - nested object
{
"_id": "publisher123",
"name": "MongoDB Press",
"headquarters": {
"street": "123 Data Lane",
"city": "San Francisco",
"state": "CA",
"country": "USA",
"zip": "94107"
}
}
Reference
Option 1: Array of references within the parent document
โ Use when - the app primarily reads publisher documents
// publishers
{
"_id": "publisher123",
"name": "MongoDB Press",
"headquarter_id": "hq1"
}
// headquarters
{
"_id": "hq1",
"street": "123 Data Lane",
"city": "San Francisco",
"state": "CA",
"country": "USA",
"zip": "94107"
}
Option 2: a child document to reference the parent document
โ Use when - the app primarily reads headquarters documents
// publishers
{
"_id": "publisher123",
"name": "MongoDB Press"
}
// headquarters
{
"_id": "hq1",
"publisher_id": "publisher123",
"street": "123 Data Lane",
"city": "San Francisco",
"state": "CA",
"country": "USA",
"zip": "94107"
}
One-to-Many
Example: one printed book has many reviews
Embedded
Good when
No duplication of information - i.e, in this case, review can be attached to one book only
The option on many sides cannot exist by itself - i.e, review cannot exist without the printed book
Bad when
- Children are an unbounded array => it can make the document super large
Option 1: As an array
{
"_id": "book0456",
"title": "Practical MongoDB Aggregations",
"author": "Paul Done",
"reviews": [
{
"user_id": "user0956",
"title": "Lorem Ipsum",
"review_text": "dolor sit amet...",
"rating": 3
},
{
"user_id": "user0345",
"title": "Ut enim",
"review_text": "ad minim veniam...",
"rating": 4
}
]
}
Option 2: As an object
{
"_id": "book0456",
"title": "Practical MongoDB Aggregations",
"author": "Paul Done",
"reviews": {
"user0956": {
"title": "Lorem Ipsum",
"review_text": "dolor sit amet...",
"rating": 3
},
"user0345": {
"title": "Ut enim",
"review_text": "ad minim veniam...",
"rating": 4
}
}
}
Reference
Option 1: Array of references to child documents in parent document
Use when: The list of children is small and often accessed together with the parent
// books
{
"_id": "book0456",
"title": "Practical MongoDB Aggregations",
"author": "Paul Done",
"reviews": [
"review001",
"review002"
]
}
// reviews
[
{
"_id": "review001",
"user_id": "user0956",
"title": "Lorem Ipsum",
"review_text": "dolor sit amet...",
"rating": 3
},
{
"_id": "review002",
"user_id": "user0345",
"title": "Ut enim",
"review_text": "ad minim veniam...",
"rating": 4
}
]
Option 2: a child document to reference the parent document - recommended in this case due to an unbounded array โ
Use when:
The number of children is large or grows unbounded.
You rarely need all the children at once.
// books
{
"_id": "book0456",
"title": "Practical MongoDB Aggregations",
"author": "Paul Done"
}
// reviews
[
{
"_id": "review001",
"book_id": "book0456",
"user_id": "user0956",
"title": "Lorem Ipsum",
"review_text": "dolor sit amet...",
"rating": 3
},
{
"_id": "review002",
"book_id": "book0456",
"user_id": "user0345",
"title": "Ut enim",
"review_text": "ad minim veniam...",
"rating": 4
}
]
Many-to-many
Example: a book has many authors, and an author can write many books
Embedded - recommended in this case, check the โWhen to use whichโ section above โ
Notice that here we have data duplication. However, itโs not always an issue
Option 1: As an array
{
"_id": "book0008",
"title": "Mastering MongoDB 7.0",
"authors": [
{
"author_id": "author0021",
"name": "Elie Hannouch"
},
{
"author_id": "author0087",
"name": "Malak Abu Hammad"
},
{
๐ "author_id": "author0046",
"name": "Rachelle Palmer"
}
]
}
// another book
{
"_id": "book0017",
"title": "Building Rich and Resilient Applications with MongoDB",
"authors": [
{
๐ "author_id": "author0046",
"name": "Rachelle Palmer"
}
]
}
Option 2: As an object
{
"_id": "book0008",
"title": "Mastering MongoDB 7.0",
"authors": {
"author0021": {
"name": "Elie Hannouch"
},
"author0087": {
"name": "Malak Abu Hammad"
},
"author0046": {
"name": "Rachelle Palmer"
}
}
}
Reference
Option 1: Array of references to child documents in parent document
// books
[
{
"_id": "book0008",
"title": "Mastering MongoDB 7.0",
"authors": [
"author0021",
"author0087",
๐ "author0046"
]
},
{
"_id": "book0017",
"title": "Building Rich and Resilient Applications with MongoDB",
"authors": [
๐ "author0046"
]
}
]
// authors
[
{
"_id": "author0021",
"name": "Elie Hannouch"
},
{
"_id": "author0087",
"name": "Malak Abu Hammad"
},
{
"_id": "author0046",
"name": "Rachelle Palmer"
}
]
Option 2: a child document to reference the parent document
// books
[
{
"_id": "book0008",
"title": "Mastering MongoDB 7.0"
},
{
"_id": "book0017",
"title": "Building Rich and Resilient Applications with MongoDB"
}
]
// authors
[
{
"author_id": "author0021",
"name": "Elie Hannouch",
// not that unlike the one-to-many
// this is an array
๐ "books": ["book0008"]
},
{
"author_id": "author0087",
"name": "Malak Abu Hammad",
๐ "books": ["book0008"]
},
{
"author_id": "author0046",
"name": "Rachelle Palmer",
"books": ["book0008", "book0017"]
}
]