Storing Related Entities in Same Document
Complete the full lesson to earn 25 points — 50 with Pro
Work through each section, then tap “Mark as Complete” on the last one.
✦ Skip the page breaks, the wait, and see fewer ads — read each lesson on a single page with Pro
Storing Related Entities in the Same Document: A Deep Dive into Document Modeling
Introduction: Why Data Modeling Matters in Non-Relational Databases
When developers transition from relational database management systems (RDBMS) to non-relational or NoSQL databases, the most significant shift is not in the syntax, but in the philosophy of data modeling. In a traditional relational model, you are taught to normalize data: break entities into separate tables, establish foreign key relationships, and join them back together during retrieval. While this reduces data redundancy, it often imposes a performance tax as the application scales and the number of joins grows complex.
In the world of document-oriented databases, such as MongoDB or CouchDB, the approach is fundamentally different. Instead of normalizing, we often embrace denormalization by embedding related data directly into a single document. This technique, known as "embedding" or "denormalization," allows you to store a primary entity and its related sub-entities in a single, atomic unit of storage. This lesson explores the mechanics, advantages, and trade-offs of storing related entities in the same document, providing you with the practical knowledge needed to design high-performance, scalable data schemas.
Understanding when to embed versus when to reference is the hallmark of a skilled database architect. By the end of this lesson, you will understand how to structure your documents to match your application’s access patterns, ensuring that your data retrieval is fast, predictable, and maintainable.
The Philosophy of Embedding: Locality and Atomic Operations
The core concept behind storing related entities in the same document is data locality. In a document database, when you retrieve a document, the database engine pulls the entire object into memory. If your application frequently requires both the "User" and their "Recent Orders" to render a dashboard, storing the orders inside the user document means you can retrieve all necessary information with a single disk read.
Atomic Operations
One of the most compelling reasons to use embedding is the ability to perform atomic operations. In most document databases, updates to a single document are atomic. If you embed a list of comments within a blog post document, you can add a new comment and update the comment count in the main metadata simultaneously. If these were separate collections, you would need to implement complex transaction logic or risk data inconsistency if the application crashes between the two write operations.
Reduced Complexity in Retrieval
When data is embedded, your application code becomes simpler. Instead of writing complex JOIN queries or performing multiple round-trips to the database to fetch related records, your application receives a complete object graph in one response. This reduces the latency of your API endpoints and decreases the load on your database server, as it does not need to compute joins on the fly.
Callout: Embedding vs. Referencing (Linking) The choice between embedding and referencing is a fundamental design decision. Embedding is ideal for "contains" relationships (e.g., a "Product" has "Categories") or one-to-few relationships where the related data is small and rarely changes. Referencing is preferred for one-to-many or many-to-many relationships where the related data is large, unbounded (constantly growing), or shared across multiple primary entities.
Practical Implementation: Scenarios and Patterns
To understand how to implement this effectively, we must look at common data patterns. Let’s explore three distinct scenarios where storing related entities in the same document is the preferred approach.
Scenario 1: One-to-Few Relationships (The User Profile Pattern)
Consider a user profile system where each user has a list of addresses. While a user might have multiple addresses, the number is usually small (home, work, billing). Storing these inside the user document is highly efficient.
Example Document Structure:
{
"_id": "user_123",
"username": "jdoe",
"email": "[email protected]",
"addresses": [
{
"type": "home",
"street": "123 Maple St",
"city": "Springfield",
"zip": "62704"
},
{
"type": "work",
"street": "456 Corporate Blvd",
"city": "Springfield",
"zip": "62701"
}
]
}
In this structure, the addresses are part of the user document. If the application needs to display the user's profile, it automatically gets the addresses as well. There is no need for a separate addresses collection, which simplifies the database schema and reduces the number of indexes needed.
Scenario 2: Immutable or Historical Snapshots
Another powerful use case for embedding is capturing a snapshot of data at a specific point in time. For example, in an e-commerce order, you should embed the product details (name, price at time of purchase) into the order document. Even if the product price changes in the main "Products" collection later, the order document must preserve the price the customer actually paid.
Example Order Document:
{
"_id": "order_999",
"customer_id": "user_123",
"order_date": "2023-10-27T10:00:00Z",
"items": [
{
"product_id": "prod_abc",
"name": "Wireless Mouse",
"price_at_purchase": 25.99,
"quantity": 1
}
],
"total": 25.99
}
By embedding the product name and price, you ensure the order record remains accurate and self-contained. This is a classic example of denormalization that serves a business requirement for auditability.
Scenario 3: Aggregated Metadata
Sometimes you embed data not because it is a child entity, but because it represents an aggregate state. If you are building a social media platform, you might keep a list of the "top 5 recent commenters" inside the post document. This allows the UI to render the post with its most active discussion without performing a query on the entire comments collection.
Step-by-Step Design Process
When designing your document model, follow these steps to ensure you are making the right choice for your specific use case.
- Map Your Access Patterns: Before defining your schema, write down the queries your application will run. Which entities are retrieved together 90% of the time?
- Evaluate Data Size: Document databases have limits on document size (e.g., 16MB in MongoDB). If your related data is unbounded (like a list of every single comment ever made on a post), do not embed. Only embed if the related data is small and finite.
- Analyze Growth Rates: Does the related data grow over time? If you expect a user to have thousands of "activity logs," embedding them in the user document will eventually lead to massive documents that are slow to update and exceed memory limits.
- Identify Update Frequency: If you embed data that is updated frequently, consider how that affects the document size. If the document grows in size on disk, the database may need to move the document to a new location, which can cause performance fragmentation.
- Draft and Test: Create a sample document and run your application queries against it. Measure the response time and the amount of data transferred.
Tip: Always favor performance for read-heavy applications. If your application reads data significantly more often than it writes, embedding is almost always the right choice because it minimizes the need for multi-document lookups.
Comparison of Modeling Approaches
The following table compares the two primary ways to handle related data in document databases.
| Feature | Embedding (Denormalization) | Referencing (Normalization) |
|---|---|---|
| Read Performance | High (Single document fetch) | Lower (Requires multiple queries/joins) |
| Data Integrity | Manual (Application-level) | High (Database-level constraints) |
| Data Size | Risks document growth limits | Unlimited growth potential |
| Complexity | Simple for small, related sets | Higher due to application logic |
| Use Case | One-to-few, read-heavy data | One-to-many, many-to-many, unbounded data |
Best Practices and Industry Standards
To avoid the "spaghetti schema" problem, follow these established industry best practices when working with embedded models.
1. Maintain Control Over Document Size
Always have a plan for when an embedded array might grow too large. For example, if you are embedding comments in a post, implement a "pagination" logic where you only store the most recent 10 comments in the document, and move older comments to a separate, referenced collection. This keeps the primary document lean and performant.
2. Design for the UI
Document databases are often designed "inside-out" to match the needs of the UI. If your frontend dashboard needs a specific shape of data to render efficiently, design your document to match that shape. This reduces the amount of data transformation your backend code needs to perform.
3. Handle Updates Carefully
When updating an embedded array, use the specific operators provided by your database (such as $push or $pull in MongoDB). These operators allow you to modify the array without needing to pull the entire document into your application memory, modify it, and save it back. This is more efficient and prevents race conditions.
4. Use Versioning for Embedded Data
If you embed data that changes, such as user settings or product descriptions, consider including a version field. This allows your application to handle legacy documents that might have older, deprecated structures, preventing runtime errors when you roll out schema updates.
Common Pitfalls and How to Avoid Them
Even experienced developers fall into traps when denormalizing data. Here are the most frequent mistakes and how to avoid them.
Pitfall 1: Unbounded Growth
The most common mistake is embedding an array that grows indefinitely. If you have an Event document and you try to embed every Attendee who registers, that document will eventually hit the size limit, potentially causing your application to crash when a new user tries to register.
- The Fix: If the relationship is one-to-many and the "many" side is large, use references.
Pitfall 2: Over-Denormalization
Some developers believe that "no joins" means "no references ever." They try to embed everything. This leads to massive documents that are slow to load and contain redundant, duplicated data that is difficult to keep in sync.
- The Fix: Use the "Rule of Thumb": If the data is shared across multiple entities (like a Product that appears in many Orders), keep the Product in its own collection and reference it.
Pitfall 3: Ignoring Consistency Requirements
Because embedded data lives inside a document, updating it in multiple places if the data is duplicated can be risky. If you store user information inside every order document, what happens when the user changes their email address? You would have to perform a massive update across millions of documents.
- The Fix: Distinguish between data that is immutable (like the price of an item at the time of purchase) and data that is mutable (like a user's contact information). Only embed immutable data for historical records.
Warning: Be extremely cautious about embedding mutable data that is repeated across many documents. If that data changes, you will face an "update anomaly" where some documents are updated and others are not, leading to corrupted business logic.
Deep Dive: When Should You Use References?
While this lesson focuses on embedding, it is impossible to understand embedding without understanding the alternative. You should reach for references (linking) under the following circumstances:
- Many-to-Many Relationships: If you have authors and books, where an author has many books and a book can have many authors, embedding is nearly impossible to manage. Use a reference array of IDs in both documents or a join collection.
- Large Data Volumes: If the related data is large (e.g., thousands of items), the overhead of reading the entire document outweighs the benefit of avoiding a second query.
- Frequent Independent Updates: If the related data is updated independently and frequently, the cost of rewriting the parent document every time the child changes will hurt your write performance.
Practical Example of Referencing
If you have a Blog and Comments, and a blog can have thousands of comments, your model should look like this:
Blog Document:
{
"_id": "blog_1",
"title": "My Thoughts on NoSQL",
"author": "jdoe"
}
Comment Document:
{
"_id": "comment_1",
"blog_id": "blog_1",
"text": "Great read!",
"user": "reader_x"
}
In this case, you query for comments by filtering on the blog_id index. This is a highly efficient way to handle large, growing sets of data while maintaining the benefits of a document database.
Advanced Considerations: Migrations and Schemas
When you design with embedded models, you are implicitly tying your schema to your application requirements. As these requirements change, your schema must evolve.
Schema Versioning
It is a standard practice to include a schema_version field in your documents. If you decide to move from an embedded model to a referenced model (or vice versa) for a specific entity, you can use the schema_version to handle the transition gracefully. Your application code can check the version and, if it encounters an old structure, perform a "lazy migration" where it updates the document to the new format upon the next read.
The Cost of Fragmentation
In some document databases, updating an embedded array can cause the document to grow beyond its allocated space on disk. This results in "document relocation," where the database moves the document to a new location. If this happens frequently, it leads to disk fragmentation and performance degradation.
- How to mitigate: Use "padding" or pre-allocation if your database supports it, or ensure your write patterns are optimized to avoid small, incremental updates that trigger re-allocation.
Frequently Asked Questions
Q: Does embedding make my database faster? A: It makes reads faster by reducing the need for joins. However, it can make writes slower if the document becomes very large or if it causes frequent re-allocations on disk. It is a trade-off.
Q: Can I search inside an embedded array? A: Yes, most modern document databases provide powerful indexing capabilities for fields inside arrays. For example, in MongoDB, you can create a multi-key index on an embedded array to make searching for specific items extremely fast.
Q: How do I handle ordering of embedded items? A: If you embed a list of items, the order is preserved in the document. If you need to maintain a specific sort order (e.g., newest comments first), you can use application logic to sort the array before saving or use database-level operators to insert items at the beginning of the array.
Q: What if I need to perform a query across multiple embedded collections? A: This is where document databases become difficult. If your query requirements involve complex filters across multiple levels of embedded data, you might be pushing the limits of the document model. In these cases, reconsider if the data should be flattened or if you need a different storage architecture.
Key Takeaways
To summarize the core principles of storing related entities in the same document:
- Prioritize Access Patterns: Design your data model based on how your application retrieves data. If you fetch it together, store it together.
- Respect the "One-to-Few" Rule: Embedding is best suited for relationships where the child entities are limited in number and do not grow indefinitely.
- Ensure Data Integrity: Use embedding for immutable data (snapshots) to preserve historical accuracy. Use references for mutable data that needs to remain consistent across the system.
- Monitor Document Size: Always keep an eye on the growth of your documents. Avoid embedding arrays that will grow without bound, as this will eventually lead to performance bottlenecks.
- Leverage Atomic Updates: One of the strongest features of document databases is atomic modification of a single document. Use this to maintain consistency in your embedded data without needing complex transactions.
- Don't Fear Refactoring: If your application requirements change, you may need to move from an embedded model to a referenced model. Use versioning to manage these transitions without downtime.
- Balance Read and Write Performance: Remember that embedding favors read performance. If your application is write-heavy and involves large, complex objects, referencing may provide better overall performance and scalability.
By carefully considering these factors, you can design a schema that is not only performant and scalable but also intuitive to work with as your application evolves. Remember, the best data model is the one that best serves the specific queries and business needs of your unique application.
Reach the last section to complete this lesson and earn points — you're on section 1 of 10.
- Introduction to Cosmos DB Data Modeling
- Introduction to Cosmos DB Data Modeling Quiz5q
- Multiple Entity Types in Same Container
- Multiple Entity Types in Same Container Quiz5q
- Storing Related Entities in Same Document
- Storing Related Entities in Same Document Quiz5q
- Denormalizing Data Across Documents
- Denormalizing Data Across Documents Quiz5q
- Referencing Between Documents
- Referencing Between Documents Quiz5q
- Partition Keys and Document IDs
- Partition Keys and Document IDs Quiz5q
- Time to Live (TTL) Configuration
- Time to Live (TTL) Configuration Quiz5q
- Document Versioning Strategies
- Document Versioning Strategies Quiz5q
- Schema Versioning Patterns
- Schema Versioning Patterns Quiz5q
- Choosing Partition Strategies
- Choosing Partition Strategies Quiz5q
- Partition Key Selection Best Practices
- Partition Key Selection Best Practices Quiz5q
- Transactions and Partition Keys
- Transactions and Partition Keys Quiz5q
- Cross-Partition Query Costs
- Cross-Partition Query Costs Quiz5q
- Data Distribution Analysis
- Data Distribution Analysis Quiz5q
- Throughput Distribution Planning
- Throughput Distribution Planning Quiz5q
- Synthetic Partition Keys
- Synthetic Partition Keys Quiz5q
- Hierarchical Partition Keys
- Hierarchical Partition Keys Quiz5q
- Throughput and Storage Requirements
- Throughput and Storage Requirements Quiz5q
- Serverless vs Provisioned Throughput
- Serverless vs Provisioned Throughput Quiz5q
- Database-Level Provisioned Throughput
- Database-Level Provisioned Throughput Quiz5q
- Granular Scale Units
- Granular Scale Units Quiz5q
- Global Distribution Costs
- Global Distribution Costs Quiz5q
- Configuring Throughput in Portal
- Configuring Throughput in Portal Quiz5q
- Gateway vs Direct Connectivity Mode
- Gateway vs Direct Connectivity Mode Quiz5q
- Creating Database Connections
- Creating Database Connections Quiz5q
- Azure Cosmos DB Emulator
- Azure Cosmos DB Emulator Quiz5q
- Connection Error Handling
- Connection Error Handling Quiz5q
- Singleton Pattern for Clients
- Singleton Pattern for Clients Quiz5q
- Global Distribution Regions
- Global Distribution Regions Quiz5q
- Threading and Parallelism
- Threading and Parallelism Quiz5q
- Arrays and Nested Objects Queries
- Arrays and Nested Objects Queries Quiz5q
- Correlated Subqueries
- Correlated Subqueries Quiz5q
- Array and Type-Checking Functions
- Array and Type-Checking Functions Quiz5q
- Mathematical and String Functions
- Mathematical and String Functions Quiz5q
- Date Functions in Queries
- Date Functions in Queries Quiz5q
- Point Operations vs Query Operations
- Point Operations vs Query Operations Quiz5q
- CRUD Point Operations
- CRUD Point Operations Quiz5q
- Patch Operations for Updates
- Patch Operations for Updates Quiz5q
- Transactional Batch Operations
- Transactional Batch Operations Quiz5q
- Bulk Operations with SDK
- Bulk Operations with SDK Quiz5q
- Optimistic Concurrency with ETags
- Optimistic Concurrency with ETags Quiz5q
- Query Pagination and Continuation
- Query Pagination and Continuation Quiz5q
- Cosmos DB Mirroring for Fabric
- Cosmos DB Mirroring for Fabric Quiz5q
- Mirroring vs Spark Connector
- Mirroring vs Spark Connector Quiz5q
- Enabling Analytical Store
- Enabling Analytical Store Quiz5q
- Synapse Spark and SQL Queries
- Synapse Spark and SQL Queries Quiz5q
- Change Data Capture in Analytical Store
- Change Data Capture in Analytical Store Quiz5q
- Azure Functions and Event Hubs Integration
- Azure Functions and Event Hubs Integration Quiz5q
- Denormalization with Change Feed
- Denormalization with Change Feed Quiz5q
- Referential Integrity with Change Feed
- Referential Integrity with Change Feed Quiz5q
- Azure AI Search Integration
- Azure AI Search Integration Quiz5q
- Azure Functions Change Feed Trigger
- Azure Functions Change Feed Trigger Quiz5q
- Consuming Change Feed with SDK
- Consuming Change Feed with SDK Quiz5q
- Change Feed Estimator
- Change Feed Estimator Quiz5q
- Denormalization via Change Feed
- Denormalization via Change Feed Quiz5q
- Aggregation Persistence with Change Feed
- Aggregation Persistence with Change Feed Quiz5q
- Read-Heavy vs Write-Heavy Indexing
- Read-Heavy vs Write-Heavy Indexing Quiz5q
- Index Type Selection
- Index Type Selection Quiz5q
- Custom Indexing Policies
- Custom Indexing Policies Quiz5q
- Composite Index Implementation
- Composite Index Implementation Quiz5q
- Index Performance Optimization
- Index Performance Optimization Quiz5q
- Response Status Codes and Metrics
- Response Status Codes and Metrics Quiz5q
- Normalized RU Consumption Monitoring
- Normalized RU Consumption Monitoring Quiz5q
- Server-Side Latency Metrics
- Server-Side Latency Metrics Quiz5q
- Data Replication Monitoring
- Data Replication Monitoring Quiz5q
- Azure Monitor Alerts Configuration
- Azure Monitor Alerts Configuration Quiz5q
- Resource Logs Implementation
- Resource Logs Implementation Quiz5q
- Partition Throughput Monitoring
- Partition Throughput Monitoring Quiz5q
- Encryption Key Management
- Encryption Key Management Quiz5q
- Network-Level Access Control
- Network-Level Access Control Quiz5q
- Data Encryption Configuration
- Data Encryption Configuration Quiz5q
- Azure RBAC for Control Plane
- Azure RBAC for Control Plane Quiz5q
- Microsoft Entra ID for Data Plane
- Microsoft Entra ID for Data Plane Quiz5q
- CORS Settings Configuration
- CORS Settings Configuration Quiz5q
- Customer-Managed Keys
- Customer-Managed Keys Quiz5q
- Always Encrypted Implementation
- Always Encrypted Implementation Quiz5q
- Data Movement Strategy Selection
- Data Movement Strategy Selection Quiz5q
- SDK Bulk Operations for Data Movement
- SDK Bulk Operations for Data Movement Quiz5q
- Azure Data Factory Pipelines
- Azure Data Factory Pipelines Quiz5q
- Kafka Connector Integration
- Kafka Connector Integration Quiz5q
- Azure Stream Analytics Integration
- Azure Stream Analytics Integration Quiz5q
- Cosmos DB Spark Connector
- Cosmos DB Spark Connector Quiz5q
Enjoying the courses?
Everything stays free. Pro shows fewer ads, doubles the points you earn on every lesson and quiz so you progress twice as fast, unlocks half of every practice exam — plus full case studies — with the Learn & Exam study modes, and lets you read each lesson on one page.
- ✓ Fewer advertisements
- ✓ 2× points per lesson & quiz
- ✓ 50% of every exam unlocked
- ✓ Learn & Exam modes
- ✓ Distraction-free lessons