Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
MongoDBintermediate

MongoDB Document Modeling

Deciding what belongs in one document, when to reference instead, and the patterns that keep documents bounded.

3 min readIntermediateUpdated Edit this page

MongoDB modelling starts from access patterns, not from entities. The question is not "what are the things" but "what is read and written together".

Embedding vs Referencing

Embed when the child data is read with the parent, written with the parent, and bounded in size.

// One read returns everything the order page needs.
{
  _id: ObjectId("…"),
  status: "paid",
  shippingAddress: { line1: "…", city: "…", country: "DE" },
  items: [ { sku: "TS-001", qty: 2, priceCents: 4950 } ]
}

Reference when the child data is large, unbounded, shared between parents, or accessed independently.

// Comments grow without limit — a separate collection.
{ _id: ObjectId("…"), postId: ObjectId("…"), body: "…", createdAt: ISODate("…") }

The decision table:

SignalEmbedReference
Read togetherYesNo
Child count boundedYesNo
Child updated independently and oftenNoYes
Child shared by several parentsNoYes
Combined size approaches 16 MBNoYes

Duplicating fields deliberately

Documents often duplicate a few fields from a referenced entity so the common query needs one read:

{
  _id: ObjectId("…"),
  customerId: ObjectId("…"),
  customerName: "Acme GmbH",   // duplicated for the order list view
  totalCents: 19900
}

This is a legitimate trade: one fewer lookup on a hot path, in exchange for updating the duplicate when the source changes. Duplicate only fields that change rarely, and record where each duplicate lives so an update path exists.

Useful patterns

Bucketing. Instead of one document per reading, store a bucket per hour or per thousand readings. It cuts document count and index size dramatically for time-series-like data.

{
  sensorId: "s-1001",
  hour: ISODate("2026-07-31T10:00:00Z"),
  count: 3600,
  readings: [ { t: 0, v: 21.4 }, { t: 1, v: 21.5 } ]
}

Computed values. Store the aggregate you query for — an order total, a comment count — updated with $inc when children change. Avoids recomputing on every read.

Subset. Embed the most recent or most relevant N children and keep the full set in another collection, so the common view needs one read.

The outlier exception. When most documents are small but a few are enormous, mark the outliers with a flag and handle them separately rather than designing the whole collection around them.

Field names count

Field names are stored in every document. On collections with hundreds of millions of small documents, shortening long names measurably reduces storage and working-set size. Balance that against readability — this matters at scale and is premature optimisation below it.