See It Flow
Interactive guide · 9 chapters · ~24 min read

MongoDB Production Engineering

MongoDB's flexible schema doesn't remove the need for schema design — it just moves the cost from migration scripts to query-time index misses and oversized documents. This section covers schema design, index strategy, aggregation performance, replication, and sharding as they actually behave under production load.

Mangesh MandlikLast updated:
01 · Modeling

Schema Design

MongoDB's flexibility is a tool, not an excuse — the shape of your documents still determines your application's performance ceiling.

Model for your queries

Design documents around how data is read, not just how it's conceptually related — MongoDB rewards read-optimized, denormalized shapes.

Schema validation

$jsonSchema validation rules enforce required fields and types at the database level, without giving up per-document flexibility entirely.

02 · Speed

Index Optimization

The right index turns a multi-second query into a sub-millisecond one. The wrong index just slows down every write.

Compound index field order

An index on { customerId: 1, createdAt: -1 } serves queries filtering on customerId alone, or customerId + createdAt range — but not createdAt alone. Order matters (the ESR rule: Equality, Sort, Range).

Partial indexes

{ partialFilterExpression: { status: 'pending' } } — index only the documents a query actually needs, keeping the index small.

Covered queries

If every field a query needs is present in the index itself, MongoDB can answer without fetching the document at all.

03 · Analytics at scale

Aggregation Performance

An aggregation pipeline can be as fast as an indexed query or as slow as scanning the whole collection, depending on stage order.

$match early, $match often

Placing $match (and $sort, when it can use an index) as early as possible lets MongoDB use indexes and shrinks the working set for every later stage.

$lookup cost

A $lookup join scans the foreign collection per input document unless it can use an index on the foreign field — always index the field a $lookup joins on.

04 · The tool

explain()

explain() shows the plan MongoDB chose and, in executionStats mode, how many documents it actually examined.

javascriptAlways check executionStats — it shows nReturned vs totalDocsExamined, which usually explains the real bottleneck.
db.orders.find({ customerId: 42 }).explain('executionStats')

totalDocsExamined vs nReturned

A big gap between documents examined and documents returned is the #1 sign a query needs a better index.

winningPlan.stage

COLLSCAN means no usable index was found; IXSCAN means an index narrowed the search before fetching documents.

05 · Availability

Replication

Replica sets are how MongoDB survives a machine dying and how it scales reads horizontally.

Primaryoplog writerSecondary Areplays oplogSecondary Breplays oplogElectedpromoted on failover
A primary streams its oplog to secondaries. Reads can be served from any caught-up secondary with the right read preference.

Read preference

primary, primaryPreferred, secondary, secondaryPreferred, or nearest — controls which member(s) can serve a read, trading recency for read scalability.

Oplog sizing

The oplog is a capped collection — if it's too small, a secondary that falls behind (e.g. after maintenance) may not be able to catch up and needs a full resync.

06 · Scale

Sharding

Sharding solves storage and write-throughput limits, but a poor shard key can make it actively worse than a single server.

Clientmongosrouterconfig serverShard 1A–MShard 2N–Z
mongos routes targeted queries to one shard; queries without the shard key scatter-gather across all of them.

Choosing a shard key

Needs high cardinality and even write distribution. A monotonically increasing key (like a timestamp) creates a 'hot shard' that absorbs all new writes.

Chunk migrations

The balancer moves chunks between shards to keep data evenly distributed — this consumes I/O and should be scheduled during low-traffic windows for large migrations.

07 · Consistency

Read & Write Concerns

Write concern controls durability guarantees per write; read concern controls consistency guarantees per read — both are configurable, not fixed platform-wide.

w: majority

Waits for a majority of voting replica set members to acknowledge before confirming a write — survives a primary failure immediately after the write. It is the implicit default in MongoDB 5.0+ (with documented exceptions, such as some arbiter-containing topologies, and configurable cluster-wide defaults); earlier versions default to w: 1.

readConcern: majority

Guarantees a read only returns data acknowledged by a majority — avoiding reads of data that could later be rolled back after a failover.

08 · Visibility

Monitoring

MongoDB exposes rich operational data through server status and profiling — the discipline is watching it before it becomes an incident.

Database Profiler

Logs slow operations above a configurable threshold — the first place to look for a specific slow query in production.

serverStatus / currentOp

Live view of connections, memory, and in-flight operations — the equivalent of Postgres' pg_stat_activity.

Replication lag metrics

rs.printSecondaryReplicationInfo() shows how far behind each secondary is — critical for read-preference correctness and failover readiness.

09 · Incidents & playbook

Common Production Issues & Debugging

A handful of failure modes account for most MongoDB incidents — and a repeatable order of operations turns a stressful incident into a checklist.

Unbounded array growth

An embedded array that grows without limit (e.g. comments on a post) eventually approaches the 16 MB document size limit and slows every read/write of that document.

Missing shard key in queries

Scatter-gather queries that hit every shard, when a targeted query using the shard key would have hit just one.

Oplog too small for maintenance windows

A secondary taken down for maintenance falls off the back of the oplog and requires a full, expensive resync instead of catching up.

Key takeaways
  • Check the profiler and currentOp for long-running or blocked operations first.
  • Run explain('executionStats') on the slow query — look for COLLSCAN and large totalDocsExamined vs nReturned gaps.
  • Check replication lag if reads use a non-primary read preference.
  • Check shard targeting — is the query using the shard key, or scatter-gathering?
  • Only then consider a schema or index change — never guess before measuring.