Claude Fable 5.1 & GPT-6 Astra packages are live

Mongodb

Free · MIT

MongoDB document modelling and operational rules — embed versus reference, indexes, write concern, transactions, and the failure modes of a…

186 lines6.6 KB Kimi Database
Target models
Kimi K3Kimi K2.6Kimi K2 FamilyFuture Kimi Models
Name
mongodb
Category
Database
Description
MongoDB document modelling and operational rules — embed versus reference, indexes, write concern, transactions, and the failure modes of a schemaless store.
License
MIT
Author
Agent.md maintainers
Last verified
2026-08-23
Reviewed by
unreviewed

#Scope contract

FILE_ISOLATION: Modify only files inside the scope the task names; report any out-of-scope change instead of making it.


#Purpose

Rules for MongoDB. "Schemaless" means the schema lives in application code instead of the database — it does not mean there isn't one. Every rule here exists because the database will not stop you.

Choose MongoDB for genuinely document-shaped data with varying structure. If the data is relational and you are joining it with $lookup on every read, you chose wrong. → Database/schema-design


#Embed or reference

The single most consequential modelling decision.

Embed whenReference when
Read together, alwaysRead independently
The child has no life of its ownThe child is queried on its own
Bounded cardinality (tens, not thousands)Unbounded growth
Updated togetherUpdated at different rates
js
// Embed — an order's line items are never read without the order
{ _id, customerId, items: [{ sku, qty, priceCents }], totalCents }

// Reference — a customer's orders are unbounded and queried alone
{ _id, orderId, customerId, ... }

Never embed an unbounded array. A document has a 16 MB hard limit, and a growing array forces document relocation and rewrites the whole document on every push. An array that grows with user activity — comments, events, log lines — belongs in its own collection.

Where you need the last N of an unbounded set, use the subset pattern: embed the most recent few for fast reads and keep the full set in a separate collection.


#Indexes

js
db.orders.createIndex({ tenantId: 1, createdAt: -1 });
db.orders.find({ tenantId: t }).sort({ createdAt: -1 }).explain("executionStats");

Read executionStats and check:

FieldWant
stageIXSCAN, not COLLSCAN
totalKeysExamined vs nReturnedClose to 1:1
totalDocsExaminedZero for a covered query
hasSortStageAbsent — an in-memory sort fails above 32 MB

The ESR rule orders compound index keys: Equality, then Sort, then Range. A compound index serves any prefix of its keys, so {a:1, b:1, c:1} also serves queries on {a} and {a,b} — do not create those separately.

Never leave an in-memory sort in a hot query. Above 32 MB it errors outright rather than degrading. → Database/indexes


#Write concern and durability

The defaults are not what most teams assume.

js
// Acknowledged by a majority, and durable on disk
await coll.insertOne(doc, { writeConcern: { w: "majority", j: true } });
ConcernMeaning
w: 1Primary acknowledged only — lost on failover
w: "majority"Committed to a majority; survives an election
j: trueWritten to the journal, survives a crash
readConcern: "majority"Never reads data that could be rolled back

Never use w: 1 for data whose loss matters. An election after a w: 1 write can roll it back with no error ever reaching the client.

For read-after-write in the same session, use a causally consistent session rather than reading from the primary by convention:

js
const session = client.startSession({ causalConsistency: true });

#Transactions

Multi-document transactions exist (replica sets and sharded clusters) but are far more expensive than in a relational engine, and they have a default 60-second limit.

Prefer a document model where the atomic unit is one document — that is the point of embedding. Reach for a transaction only when a genuine invariant spans collections. → Database/transactions


#Schema validation

The database will happily store { price: "twelve" }. Add validation:

js
db.createCollection("orders", {
  validator: { $jsonSchema: {
    bsonType: "object",
    required: ["tenantId", "totalCents", "createdAt"],
    properties: {
      totalCents: { bsonType: "long", minimum: 0 },
      createdAt:  { bsonType: "date" },
    },
  }},
  validationLevel: "strict",
  validationAction: "error",
});

Store money as integer minor units (long), never double. Store dates as BSON date, never as a string — string dates sort lexicographically and cannot use date operators.


#Anti-patterns

Anti-patternWhy it failsFix
Unbounded embedded array16 MB document cap; full rewrite per pushSeparate collection, or subset pattern
Using MongoDB for relational data$lookup on every readUse a relational database
No schema validationType drift reaches production silently$jsonSchema validator
w: 1 for important writesSilently rolled back on failoverw: "majority", j: true
Dates as stringsLexicographic sort; no date operatorsBSON date
Money as doubleBinary rounding errorlong minor units
Index per queryWrite amplification; RAM pressureCompound indexes by ESR
Duplicate index and its prefixRedundant maintenance costPrefix is served by the compound index
In-memory sort in a hot pathHard 32 MB failureIndex covering the sort
Transactions used as the defaultFar costlier than relationalModel so one document is atomic
$where / unindexed $regexFull collection scanAnchored regex on an indexed field

#Checklist

  • Verify: Embed/reference decided per relationship, with cardinality stated
  • Verify: No embedded array grows without bound
  • Verify: Every hot query verified with explain("executionStats") showing IXSCAN
  • Verify: Compound indexes follow Equality → Sort → Range
  • Verify: No redundant indexes that duplicate a compound prefix
  • Verify: No in-memory sorts on hot paths
  • Verify: Important writes use w: "majority", j: true
  • Verify: Read-after-write uses a causally consistent session
  • Verify: $jsonSchema validation is enabled with validationAction: "error"
  • Verify: Money stored as integer minor units; dates stored as BSON dates
  • Verify: Transactions used only for invariants that genuinely span documents