MongoDB performance at scale requires understanding the WiredTiger storage engine’s behaviour, designing for horizontal scaling with sharding, and using Atlas’s operational tooling to find and fix problems before they become outages. Read preferences control which replica set members handle reads β enabling geographic locality, read scaling, and reporting workloads without impacting the primary. Write concerns control durability guarantees. Sharding distributes data across multiple servers when a single node’s capacity is insufficient. This lesson covers these advanced operational patterns that production MongoDB deployments rely on.
Read Preference Modes
| Mode | Reads From | Use For | Staleness Risk |
|---|---|---|---|
primary |
Primary only | All writes and reads requiring latest data | None β always current |
primaryPreferred |
Primary if available, else secondary | Tolerate brief primary unavailability | Low |
secondary |
Secondary only | Reporting, analytics, background jobs | Up to replication lag (usually <1s) |
secondaryPreferred |
Secondary if available, else primary | Read scaling for mostly-read workloads | Low |
nearest |
Lowest network latency member | Multi-region: reads from closest DC | Varies by region replication lag |
Write Concern Options
| Write Concern | Acknowledges After | Durability | Latency |
|---|---|---|---|
{ w: 0 } |
Immediately (no ack) | None β fire and forget | Minimal |
{ w: 1 } |
Primary write | Low β primary could crash before replication | Low |
{ w: 'majority' } |
Majority of replica set members | High β survives primary failure | Replication lag added |
{ w: 'majority', j: true } |
Majority + journal flush | Highest β survives crash + majority failover | Highest |
secondary read preference see data that may be seconds old. For most MEAN Stack applications, secondaryPreferred is a reasonable choice for read scaling β occasional stale reads are acceptable, and the primary is used as fallback.Task.find(filter).read('secondary') (Mongoose) sets read preference for one query. This allows mixing: write operations and consistency-sensitive reads use the default (primary), while analytics queries, report generation, and export operations explicitly use secondary to offload the primary. This is more precise than setting a connection-level read preference that applies to all queries.Complete Advanced MongoDB Operations
const mongoose = require('mongoose');
const Task = require('../models/task.model');
// ββ Per-operation read preference βββββββββββββββββββββββββββββββββββββββββ
// Primary for consistency-critical reads (after a write)
async function getTaskAfterUpdate(id) {
return Task.findById(id)
.read('primary') // ensure we see the write
.lean();
}
// Secondary for analytics and reporting β offloads the primary
async function generateTaskReport(userId) {
return Task.aggregate([
{ $match: { user: new mongoose.Types.ObjectId(userId) } },
{ $group: {
_id: '$status',
count: { $sum: 1 },
avgAge: { $avg: { $subtract: [new Date(), '$createdAt'] } },
}},
]).read('secondary');
}
// Nearest for multi-region: read from closest data centre
async function getUserTasks(userId) {
return Task.find({ user: userId }).read('nearest').lean();
}
// ββ Write concern per operation βββββββββββββββββββββββββββββββββββββββββββ
// Low-durability for non-critical writes (user activity tracking)
async function recordTaskView(taskId, userId) {
return Task.updateOne(
{ _id: taskId },
{ $inc: { viewCount: 1 }, $set: { lastViewedBy: userId } },
{ writeConcern: { w: 1 } } // just primary ack β a missed view doesn't matter
);
}
// High-durability for critical writes (payment, deletion)
async function permanentlyDeleteTask(taskId) {
return Task.deleteOne(
{ _id: taskId },
{ writeConcern: { w: 'majority', j: true } } // replicated + journaled
);
}
// ββ Collation β language-aware string sorting βββββββββββββββββββββββββββββ
// Sort tasks alphabetically in French locale (handles accents correctly)
async function getTasksSorted(userId) {
return Task.find({ user: userId })
.collation({ locale: 'fr', strength: 1 }) // case-insensitive French sort
.sort({ title: 1 })
.lean();
}
// ββ Partial index for soft-delete pattern ββββββββββββββββββββββββββββββββ
// In schema definition:
// taskSchema.index(
// { user: 1, createdAt: -1 },
// { partialFilterExpression: { deletedAt: { $exists: false } } }
// );
// This index only contains non-deleted tasks β smaller, faster for common queries
// ββ Atlas Performance Advisor β interpreting suggestions βββββββββββββββββ
// Atlas surfaces these via its UI; here's how to check manually:
async function checkSlowQueries() {
const admin = mongoose.connection.db.admin();
const profile = await mongoose.connection.db
.collection('system.profile')
.find({ millis: { $gt: 100 } })
.sort({ ts: -1 })
.limit(10)
.toArray();
return profile.map(op => ({
operation: op.op,
collection: op.ns,
durationMs: op.millis,
docsExamined:op.docsExamined,
docsReturned:op.nreturned,
keysExamined:op.keysExamined,
planSummary: op.planSummary,
}));
}
// Enable profiling for slow queries (100ms threshold)
async function enableSlowQueryProfiling() {
await mongoose.connection.db.command({
profile: 1,
slowms: 100,
sampleRate: 0.5, // profile 50% of slow queries to reduce overhead
});
}
// ββ Connection monitoring βββββββββββββββββββββββββββββββββββββββββββββββββ
mongoose.connection.on('commandStarted', event => {
if (['find', 'aggregate', 'update', 'insert', 'delete'].includes(event.commandName)) {
// Log command for query audit
}
});
mongoose.connection.on('commandSucceeded', event => {
if (event.duration > 100) {
logger.warn('Slow MongoDB command', {
command: event.commandName,
durationMs: event.duration,
});
}
});
How It Works
Step 1 β Secondary Reads Offload the Primary
In a 3-node replica set (1 primary, 2 secondaries), directing analytics queries to secondary read preference means the primary only handles writes and latency-sensitive reads. A reporting query that takes 5 seconds and accesses 100,000 documents runs on a secondary without impacting the primary’s CPU, memory, or I/O β it does not slow down the 50ms API response times on the primary. This is the most straightforward form of read scaling before considering sharding.
Step 2 β Write Concern Majority Survives Primary Failures
w: 'majority' means the write is acknowledged only after it has been replicated to a majority of replica set members. In a 3-node set, that means 2 nodes. If the primary crashes immediately after the write, one of the secondaries has the data and will become the new primary without data loss. With w: 1 (primary ack only), a crash before replication means the write is lost β even though the application received a success acknowledgement.
Step 3 β Collation Enables Correct Language-Aware Sorting
String sorting in MongoDB uses raw byte comparison by default β “Γ
ngstrΓΆm” sorts after “Zebra” because ‘Γ
’ has a higher code point than ‘Z’. Collation specifies a locale that defines language-specific rules: case folding, accent normalisation, and character equivalences. { locale: 'fr', strength: 1 } sorts case-insensitively with accents treated as equivalent to their base characters β “cafΓ©” and “Cafe” sort together as expected in a French language UI.
Step 4 β Partial Indexes Reduce Index Size and Maintenance Cost
A partial index on { user: 1, createdAt: -1 } with partialFilterExpression: { deletedAt: { $exists: false } } only indexes non-deleted documents. For a soft-delete pattern where 30% of documents are deleted, the index is 30% smaller and 30% cheaper to maintain on writes. Queries that always include { deletedAt: { $exists: false } } in their filter will use this index β saving I/O for both reads and write operations.
Step 5 β MongoDB Monitoring Events Track Command Performance
Mongoose exposes MongoDB’s Command Monitoring protocol via connection events: commandStarted, commandSucceeded, commandFailed. The commandSucceeded event includes the command duration in milliseconds. Logging slow commands (over 100ms) in production β without enabling the full slow query profiler β provides targeted visibility into database performance with minimal overhead. Atlas’s Performance Advisor also analyses these patterns automatically and suggests indexes.
Quick Reference
| Task | Code |
|---|---|
| Read from secondary | Task.aggregate([...]).read('secondary') |
| Read from nearest | Task.find(f).read('nearest') |
| High-durability write | { writeConcern: { w: 'majority', j: true } } |
| Fire-and-forget write | { writeConcern: { w: 0 } } |
| Language-aware sort | .collation({ locale: 'fr', strength: 1 }).sort({ title: 1 }) |
| Enable slow query log | db.command({ profile: 1, slowms: 100 }) |
| Read slow query log | db.collection('system.profile').find({ millis: { $gt: 100 } }) |
| Monitor slow commands | connection.on('commandSucceeded', e => { if (e.duration > 100) warn(...) }) |