Start from access patterns
List the main operations with their frequency:
| Operation | Frequency | Data needed |
|---|---|---|
| Show order with lines | Very high | Order + lines + customer name |
| Add order line | High | Order |
| Customer order history | Medium | Orders by customer, recent first |
| Update customer address | Low | Customer |
Design documents so the most frequent operations touch one document.
Embed or reference
| Embed when | Reference when |
|---|---|
| Data is read together | Data is read separately |
| "Contains" relationship (order → lines) | Many-to-many or shared entities (products, customers) |
| Child count is bounded and small | Child list grows without bound (comments, events) |
| Updates are mostly to the whole aggregate | Child data updated independently and often |
Document size limit is 16 MB, but performance suffers long before that with large, frequently rewritten documents.
Useful patterns
| Pattern | Use |
|---|---|
| Extended reference | Copy a few fields (customer name) into the order to avoid joins; accept occasional updates |
| Subset | Keep the latest N items embedded (last 10 reviews), the rest in another collection |
| Bucket | Group time-series readings per device per hour into one document (or use time series collections) |
| Computed | Store totals/counters updated on write instead of aggregating on every read |
| Outlier | Handle the rare huge case (a celebrity with millions of followers) separately |
| Schema versioning | schemaVersion field to migrate documents gradually |
// Order with embedded lines and an extended reference to the customer
{
_id: ObjectId("..."),
customer: { id: 1042, name: "A. Kumar" },
orderDate: ISODate("2026-10-25"),
lines: [ { sku: "P-100", qty: 2, price: 499 }, { sku: "P-220", qty: 1, price: 1299 } ],
total: 2297,
schemaVersion: 2
}
Validate structure
db.runCommand({
collMod: "orders",
validator: { $jsonSchema: {
bsonType: "object",
required: ["customer", "orderDate", "lines"],
properties: { lines: { bsonType: "array", maxItems: 500 } }
}},
validationLevel: "moderate"
})
Review checklist for a design
- Do the top operations need only one document (or one targeted query)?
- Can any array grow without limit?
- Are duplicated fields acceptable to update on change?
- Do indexes support the queries (see the WiredTiger/index post)?
- If sharding later, does the model work with a sensible shard key?
Tip
Prototype with realistic data volumes and run the top queries with
explain(). Schema problems show up quickly in documents examined and document sizes.
Checklist
- Access patterns listed with frequency
- Embed/reference decided per relationship
- Unbounded arrays avoided
- Patterns applied where they fit
- Schema validation on key collections
- Prototype tested with explain()