Skip to main content

From Relational to Document: A Practical Guide to Data Modeling in MongoDB

Many teams moving from SQL to MongoDB struggle because they try to force relational patterns onto a document model. This guide offers a practical approach to data modeling in MongoDB, focusing on when to embed, when to reference, and how to design for performance at scale. We draw on common patterns observed across projects, not on invented case studies. Last reviewed: May 2026.Why Relational Thinking Fails in MongoDBThe Mindset ShiftIn relational databases, normalization is a default principle: you decompose data into tables to eliminate redundancy and enforce referential integrity via foreign keys. MongoDB, by contrast, encourages embedding related data within a single document. This reduces joins (which are not supported across collections) and improves read performance. However, many developers new to MongoDB instinctively normalize everything, creating collections for every entity and linking them with manual references. This approach leads to complex application-side joins and poor performance.Common MistakesA typical mistake is

Many teams moving from SQL to MongoDB struggle because they try to force relational patterns onto a document model. This guide offers a practical approach to data modeling in MongoDB, focusing on when to embed, when to reference, and how to design for performance at scale. We draw on common patterns observed across projects, not on invented case studies. Last reviewed: May 2026.

Why Relational Thinking Fails in MongoDB

The Mindset Shift

In relational databases, normalization is a default principle: you decompose data into tables to eliminate redundancy and enforce referential integrity via foreign keys. MongoDB, by contrast, encourages embedding related data within a single document. This reduces joins (which are not supported across collections) and improves read performance. However, many developers new to MongoDB instinctively normalize everything, creating collections for every entity and linking them with manual references. This approach leads to complex application-side joins and poor performance.

Common Mistakes

A typical mistake is modeling a one-to-many relationship as separate collections with a foreign key field, then performing multiple queries to assemble the data. For example, an e-commerce system might store orders and order items in two collections, requiring an application-level join. In MongoDB, embedding order items within the order document is often more efficient, as all data needed for a single order view is retrieved in one read operation. Another common error is over-embedding, where documents grow unboundedly, exceeding the 16 MB document size limit or causing unnecessary data duplication when only a subset of embedded data is needed.

When to Embed vs. Reference

The decision to embed or reference depends on access patterns. Embed when the embedded data is always accessed together with the parent, when the relationship is one-to-few (e.g., addresses for a user), and when the embedded data does not grow without bound. Reference when the related data is large or frequently updated independently, when you need to query it separately, or when you have many-to-many relationships. A good rule of thumb: design your schema around how your application reads data, not how it stores it.

Core Frameworks for Document Modeling

The Three Document Design Principles

MongoDB's documentation outlines three high-level principles: (1) Represent data as it is used in the application, (2) avoid unnecessary complexity, and (3) design for the most common operations. In practice, this means starting with the application's query patterns and working backward to the schema. A useful framework is the "attribute pattern" for handling fields with similar characteristics, the "polymorphic pattern" for documents that share a common base but have varying fields, and the "bucket pattern" for time-series or IoT data where grouping documents by time interval reduces index size and improves write performance.

Schema Design Anti-Patterns

Several anti-patterns frequently trip up teams. The "massive array" anti-pattern occurs when an array in a document grows without bound, leading to performance degradation and potential document size issues. The "unnecessary index" anti-pattern happens when every field is indexed, slowing down writes and consuming memory. The "join everything" anti-pattern relies on application-side joins across many collections, negating MongoDB's strengths. Recognizing these early can save significant rework.

Trade-offs Between Embedding and Referencing

Embedding offers faster reads (single query) and atomic updates for the entire document, but it can lead to data duplication and update anomalies. Referencing avoids duplication but requires multiple queries or the $lookup aggregation stage (which is similar to a left outer join and can be slow on large datasets). The choice often comes down to whether the relationship is one-to-few (embed) or one-to-many/many-to-many (reference). For example, embedding comments in a blog post works well if the number of comments per post is limited; otherwise, storing comments in a separate collection with a postId reference is more scalable.

Step-by-Step Migration Process

Phase 1: Analyze Access Patterns

Before writing any code, list the top 10 queries your application performs. For each query, note which fields are returned, which are used in filters or sorts, and how often the query runs. This analysis drives schema design. For example, if a user profile page always shows the user's name, email, and recent orders together, consider embedding a summary of recent orders in the user document.

Phase 2: Sketch a Logical Schema

Start with a logical data model that groups related fields into documents. Use a whiteboard or diagramming tool to visualize relationships. Identify one-to-few relationships (embed), one-to-many (reference), and many-to-many (reference with a junction collection or array of IDs). Apply the embedding rules: embed if the embedded data is small, rarely changes, and is always accessed with the parent. Otherwise, reference.

Phase 3: Normalize Only Where Necessary

Unlike relational modeling, you should denormalize by default in MongoDB. That means duplicating data like a user's name across multiple documents (e.g., in order documents) to avoid joins. This duplication is acceptable if the data changes infrequently and you have a strategy to update it when it does (e.g., using a background job or a multi-document transaction). For frequently changing fields, keep them in a single collection and reference them.

Phase 4: Prototype and Benchmark

Create a small prototype with realistic data volumes and run your top queries. Use MongoDB's explain() method to check for collection scans, index usage, and document sizes. Adjust the schema based on performance results. For instance, if a query that filters on an embedded field is slow, consider adding an index on that field or moving it to a separate collection.

Tools, Indexing, and Operational Considerations

Indexing Strategies for Document Models

Indexes in MongoDB are crucial for performance. For document models, you often need compound indexes that match the query's filter, sort, and projection. For example, if you frequently query orders by userId and sort by date, create a compound index on {userId: 1, date: -1}. When embedding, you can index embedded fields using dot notation (e.g., "items.sku"). Be mindful of index size: each index consumes RAM and slows writes. Use the "index intersection" feature sparingly; prefer compound indexes over multiple single-field indexes.

Tools for Schema Design and Migration

Several tools can help. MongoDB Compass provides a visual schema analyzer that samples documents and shows field distributions. The MongoDB Shell (mongosh) allows you to run aggregation pipelines to test queries. For migrations, use the mongoexport/mongoimport utilities or write scripts using the MongoDB drivers. Consider using the MongoDB Schema Validator to enforce document structure in production.

Operational Realities: Document Size and Growth

MongoDB documents have a 16 MB limit. For most applications, this is generous, but embedded arrays can grow unexpectedly. Monitor document sizes using the $bsonSize aggregation operator. If documents approach the limit, consider splitting the embedded array into a separate collection. Also, be aware that document growth can cause storage fragmentation; use the "power of 2 sizes" allocation (default in WiredTiger) and compact collections periodically if needed.

Scaling Your Document Model

Sharding Considerations

When scaling horizontally, your choice of shard key is critical. The shard key should be a field that distributes writes evenly across shards and is used in most queries to enable targeted operations. For document models, common shard keys include userId (for user-centric apps) or a date field (for time-series data). Avoid monotonically increasing keys like ObjectId as the sole shard key, as they concentrate writes on one shard. If your document model embeds data, ensure the embedded data does not cause the document to exceed the shard key limits (e.g., the shard key field must be present and immutable).

Data Lifecycle and Archival

As data grows, you may need to archive old documents. MongoDB's Time to Live (TTL) indexes automatically delete documents after a specified time. For more complex archival, use the aggregation pipeline to move old data to a separate collection or an external storage tier. When archiving, consider whether embedded documents should be archived together with the parent or separately. For example, archiving an order should archive its embedded line items automatically, but if line items are referenced, you need to archive both collections.

Performance Patterns for Read-Heavy Workloads

For read-heavy applications, embedding can dramatically reduce the number of queries. However, if the embedded data is large, consider using projections to return only the needed fields. Another pattern is to store a denormalized "summary" document that is updated asynchronously. For example, a product document might embed a count of reviews rather than the full list, updated via a background job. This keeps the document small while still providing frequently accessed data.

Pitfalls, Mistakes, and Mitigations

Over-Embedding and Document Bloat

The most common pitfall is embedding too much data, leading to documents that exceed the 16 MB limit or cause slow writes because the entire document is rewritten on every update. Mitigation: Set a maximum array size (e.g., limit comments to 100 per post) and move overflow to a separate collection. Use the $slice operator to retrieve only a subset of an array.

Inconsistent Data Duplication

Denormalization introduces data duplication, which can lead to inconsistencies if not managed. For example, if you embed a user's name in order documents and the user changes their name, you must update all orders. Mitigation: Use multi-document transactions (available in replica sets) to atomically update the user document and all embedded references. Alternatively, accept eventual consistency and update in batches using a background job.

Ignoring Indexing Early

Many teams design the schema first and add indexes later, leading to slow queries in production. Mitigation: Design indexes alongside the schema. For each query pattern, create the necessary indexes before loading data. Use the "explain" plan to verify index usage.

Misunderstanding $lookup Performance

The $lookup aggregation stage performs a left outer join, but it is not as fast as a relational join. It can be slow on large collections without indexes on the foreign key field. Mitigation: Use $lookup sparingly and ensure the foreign field is indexed. For frequent joins, consider embedding instead.

Frequently Asked Questions and Decision Checklist

FAQ: How do I model a many-to-many relationship?

In MongoDB, you can model many-to-many relationships using arrays of IDs on both sides. For example, a student document has an array of courseIds, and a course document has an array of studentIds. This allows bidirectional access but requires application-level logic to keep arrays in sync. Alternatively, use a junction collection that stores pairs of IDs, similar to a relational join table. Use the array approach if the relationship is small and you often access one side; use a junction collection if the relationship is large or you need to store additional metadata (e.g., enrollment date).

FAQ: Can I use transactions in MongoDB?

Yes, MongoDB supports multi-document ACID transactions on replica sets (version 4.0+) and sharded clusters (4.2+). Use transactions when you need atomic updates across multiple documents, such as updating a user's name and all orders that embed it. However, transactions have a performance cost; avoid them for high-throughput operations. For most denormalization scenarios, eventual consistency with background updates is acceptable.

Decision Checklist

  • Is the related data always accessed together? If yes, consider embedding.
  • Does the embedded data grow without bound? If yes, reference instead of embedding.
  • Is the embedded data updated independently? If yes, reference to avoid update anomalies.
  • Do you need to query the embedded data separately? If yes, reference or create a separate collection.
  • Is the document size approaching 16 MB? If yes, split into multiple documents.
  • Are you performing many $lookup stages? If yes, consider embedding or denormalizing.

Synthesis and Next Steps

Key Takeaways

Transitioning from relational to document modeling requires a mindset shift: design for your application's read patterns, not for storage normalization. Embed by default, but reference when necessary. Use the decision checklist to evaluate each relationship. Prototype and benchmark early to catch performance issues. Indexes are your friend, but use them judiciously. Remember that MongoDB's flexibility allows you to evolve your schema over time, so don't aim for perfection on day one.

Immediate Actions

Start by analyzing your application's top queries. Sketch a logical schema using the embedding rules. Build a small prototype with realistic data and run your queries. Use MongoDB Compass to visualize the schema and identify potential issues. For existing relational databases, consider using the MongoDB Relational Migrator tool to assist with schema translation. Finally, document your schema design decisions and revisit them as your application evolves.

About the Author

This article was prepared by the editorial team for this publication. We focus on practical explanations and update articles when major practices change.

Last reviewed: May 2026

Share this article:

Comments (0)

No comments yet. Be the first to comment!