Notes
What it is
MongoDB stores documents, which are JSON-like objects saved in a binary format called BSON, grouped into collections. There is no fixed table schema: two documents in one collection may have different fields, though you can add validation rules. You query by any field, including fields nested deep inside. The company runs it as a service called Atlas.
- document database
- database → collections →
- documents (BSON = binary JSON)
- no fixed schema; optional
- JSON Schema validation
- managed: MongoDB Atlas
- API-compatible: Amazon
- DocumentDB, Azure Cosmos DB
Data model
The core idea: store together what you read together. An order with its line items and shipping address is one document, so showing the order is one read with no joins. Compare that with a relational design that needs three tables and a join. Each document has an underscore id, by default an ObjectId that starts with a timestamp, so ids roughly sort by creation time.
{ _id: ObjectId("..."),
user_id: 42,
status: "paid",
items: [
{ sku: "A1", qty: 2, price: 499 },
{ sku: "B7", qty: 1, price: 1299 } ],
ship_to: { city: "Pune", pin: "411001" },
created_at: ISODate("2026-10-24") } Embed or reference
The design question is always embed or reference. Embed when the child is read with its parent, has a small bounded count, and belongs to that one parent, like line items in an order. Reference by id when the child list grows without limit, is shared between parents, or changes on its own, like all posts by a user. A post array inside the user document would grow forever and hit the sixteen megabyte limit.
- EMBED when the child
- is read with the parent
- is bounded (≤ hundreds)
- belongs to one parent
- e.g. order → line items
- REFERENCE when the child
- grows without limit
- is shared or updated alone
- e.g. user → all their posts
Storage + indexes
Under the hood, the WiredTiger engine stores collections and indexes as compressed B-trees, with document-level concurrency and a journal for crash safety. Indexes look familiar: compound, multikey for arrays, text, geospatial and TTL indexes that delete old documents automatically. For compound indexes, follow the E S R rule: fields matched by equality first, then the sort field, then range filters.
- engine: WiredTiger
- B-tree files, compressed
- document-level concurrency
- journal (WAL) for crash safety
- indexes: single, compound,
- multikey (arrays), text,
- 2dsphere (geo), TTL, partial
- ESR rule for compounds:
- Equality, Sort, Range
Replica sets
Every production MongoDB is a replica set, usually three members. The primary takes writes and records each change in the oplog, a capped log of operations. Secondaries pull the oplog and replay it. If the primary disappears, the members hold an election using a Raft-style protocol, and the most up-to-date secondary becomes primary, typically in around ten to twelve seconds. Drivers retry writes across that gap.
- 3 members (or 5): 1 primary,
- 2 secondaries
- primary writes to the oplog,
- a capped log of operations
- secondaries pull and replay it
- primary dies → members vote,
- a secondary with the latest
- data wins (Raft-like)
- election: about 10-12 s
Write and read concern
You control durability per operation. Write concern one means only the primary confirmed, so a failover can roll that write back. Write concern majority, the default since version five, waits for most members, so the write survives any failover. On reads, read concern majority returns only data that cannot be rolled back, and read preference secondary spreads reads at the cost of seeing slightly old data.
writeConcern { w: 1 }
primary only: fast, can roll back
writeConcern { w: "majority" }
most members: survives failover
(default since 5.0)
readConcern "majority"
only data that cannot roll back
readPreference "secondary"
spreads reads, may be stale Sharding
To scale writes, you shard a collection by a shard key. Data is split into chunks, each a range of key values. Config servers keep the map of which chunk lives on which shard. The stateless mongos router reads that map and sends each query to the right shard, and a balancer moves chunks around to keep shards even. Each shard is itself a replica set.
- shard key: e.g. { user_id: 1 }
- data split into chunks
- (key ranges)
- config servers: which chunk
- lives on which shard
- mongos: stateless router
- balancer: moves chunks to
- even out shards
- each shard = a replica set
Shard key traps
The shard key decides everything. Shard on a timestamp or ObjectId and every new document lands in the last chunk, so one shard takes all writes. A hashed key spreads writes evenly, but range queries must ask every shard. A query that does not include the shard key is a scatter-gather to every shard, which gets slower as you add shards. Pick a key with many values, even spread, and present in most queries, like user id. You can reshard online since version five, but it is heavy.
- monotonic key (timestamp,
- ObjectId) → all inserts hit
- the last chunk: hot shard
- hashed key → even writes,
- but range queries scatter
- query without the key →
- scatter-gather to every shard
- best: high cardinality, even,
- in most queries (user_id)
Transactions
Writes to one document are always atomic, which is another reason to embed. Since version four, MongoDB also supports multi-document transactions with snapshot isolation, including across shards. They work, but they add latency and have a sixty second default limit. If most of your writes need transactions across many documents, that is a sign your data is relational and Postgres may suit you better.
- one document: always atomic
- (update many fields at once)
- multi-document transactions:
- replica sets (4.0), shards
- (4.2), snapshot isolation
- cost: more latency, 60 s
- default limit; design so
- most writes touch one doc
Great at
MongoDB is great for records that are naturally nested and read as a whole: product catalogs with varying attributes, user profiles, content management, game state. Adding a field needs no migration, so teams move quickly. Replication and sharding are built in. The aggregation pipeline handles reporting-style queries, and change streams let other services react to every change.
- • nested records read whole:
- catalogs, profiles, CMS
- • fields change often;
- no migrations to add one
- • built-in replication and
- sharding
- • aggregation pipeline,
- change streams, TTL, geo
- • fast for small teams
Where it hurts
Where it hurts. Joins exist, but they are limited and get slow, so many-to-many data like followers or enrolments is awkward. When you copy data into many documents, the copies drift unless you update them all. Arrays that grow without limit hit the document cap and slow updates. A wrong shard key is expensive to fix. And schemaless really means the schema lives in every piece of code that reads the data.
- • joins ($lookup) are limited
- and slow at scale
- • many-to-many relations
- • duplicated data drifts
- when you denormalise
- • 16 MB document limit;
- unbounded arrays
- • wrong shard key = pain
- • "schemaless" = schema in
- every reader’s code
Use it when
Use MongoDB when your data is shaped like documents, the shape varies or keeps changing, and most reads fetch one record by id or by a few fields. Avoid it when the data is highly relational with many joins, or when core flows like payments and inventory need transactions across many records. That is relational territory. Postgres with a JSONB column is often a fine middle ground.
- USE when
- records are nested trees
- shape varies or evolves
- reads fetch one record
- you want built-in sharding
- AVOID when
- heavy joins, many-to-many
- cross-entity transactions
- (money, inventory) → SQL
- analytics → column store
Numbers
Numbers to keep in mind. Documents max out at sixteen megabytes. A replica set can have up to fifty members but only seven vote. Elections usually complete in about ten to twelve seconds. The default write concern is majority since version five. Transactions have a sixty second default limit. And an ObjectId is twelve bytes that begin with a timestamp. A single node handles tens of thousands of simple operations a second.
max document size 16 MB
replica set members max 50, 7 vote
typical election ~10-12 s
default write concern majority (5.0+)
transaction limit 60 s default
ObjectId 12 bytes,
timestamp first
simple ops per node 10k-100k/s Say it like this
How to say it. Product listings have different attributes per category and are always read as a whole, so I store each as a MongoDB document with the attributes embedded. A three-member replica set with majority write concern so nothing is lost on failover. Shard on a hashed product id for even writes, and add an index on seller id for the seller dashboard. That covers model, durability and scale.
- "Product listings vary by
- category and are read whole,
- so MongoDB documents with
- attributes embedded. Replica
- set of 3, majority writes.
- Shard on hashed product_id
- for even writes; seller
- queries use an index on
- seller_id."