← All writing2026.06.05 · CS 411 · Database Systems

MongoDB Data Modeling: Flexible Schema Still Requires Design

Choose embedding or references from access patterns, growth bounds, update ownership, and measurable query plans.

MongoDB removes the requirement that every document share an identical shape. It does not remove the consequences of shape. A document model still decides which reads are cheap, which updates are atomic, and which data can grow without bound.

Design from access patterns

Suppose a product page always needs a product and its five latest reviews. Embedding a bounded review preview can make that page one read:

{
  _id: 42,
  name: "Mechanical Keyboard",
  price: 129,
  recentReviews: [
    { userId: 7, rating: 5, text: "Solid." }
  ]
}

The full review history can still live in a separate collection.

In plain terms: store together what the application normally reads together—but only when the embedded part has a clear size and ownership boundary.

Embed when the child belongs to the parent

Embedding fits when related data:

  • is normally read with the parent;
  • is updated with the parent;
  • has bounded growth;
  • does not need an independent lifecycle.

One document write is atomic, which can remove cross-document coordination.

Reference when growth or ownership separates

References fit many-to-many relationships, children queried independently, frequently changing shared data, and arrays that could grow indefinitely.

// reviews collection
{ _id: 9001, productId: 42, userId: 7, rating: 5, text: "Solid." }

Embedding every review would make the product document grow continually and rewrite a larger document on each addition. MongoDB documents also have a 16 MiB size limit.

Duplication is a design tradeoff, not a free optimization. If a product name is copied into ten thousand order snapshots, decide whether it is historical truth or data that must be synchronized.

Index the query shape

db.reviews.createIndex({ productId: 1, createdAt: -1 })

db.reviews.find({ productId: 42 })
  .sort({ createdAt: -1 })
  .limit(5)
  .explain("executionStats")

Check the winning plan, totalKeysExamined, totalDocsExamined, and returned count. A low execution time on a tiny collection proves little.

Indexes on arrays become multikey indexes. They are powerful, but wide or multiple arrays can generate many index entries. Index size and write cost still matter.

Flexible does not mean unvalidated

Use schema validation for required fields and types at stable boundaries. Allow deliberate variation, not accidental drift such as price being a number in one document and a string in another.

Decision table

Condition Prefer
Read and update together, bounded child set Embed
Many-to-many or independent child lifecycle Reference
Shared value changes frequently Reference or explicit synchronization
Historical snapshot must not change Deliberate duplication
Array grows without a known bound Separate collection

Review card

  • Document shape is an access-path decision.
  • Embed bounded data with shared ownership.
  • Reference independent or unbounded data.
  • Name the consistency rule for every duplicate.
  • Validate stable fields even in a flexible model.
  • Use explain("executionStats"), not intuition, to verify indexes.

Sources