MongoDB Cheatsheet

Administration

Use this MongoDB reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Stats and current operations

Start with the three stat calls, then look at what is running right now.

db.stats()                          // dataSize, storageSize, indexSize, collections
db.orders.stats()                   // per collection, plus per-index sizes
db.orders.countDocuments()
db.orders.totalIndexSize()
db.serverStatus()                   // huge. Pick fields
db.serverStatus().connections       // { current, available, totalCreated }
db.serverStatus().opcounters        // insert, query, update, delete, command
db.serverStatus().wiredTiger.cache  // "bytes currently in the cache" vs "maximum"
db.hostInfo()
db.version()
// Long-running operations, and how to stop one
db.currentOp({ "secs_running": { $gte: 5 }, "op": { $ne: "none" } })
db.currentOp({ waitingForLock: true })
db.killOp(4815162342)               // opid from currentOp

db.currentOp() and db.collection.stats() are deprecated since 6.2 in favour of the aggregation stages, which are filterable and are the versions Atlas supports:

db.aggregate([{ $currentOp: { allUsers: true } },
              { $match: { secs_running: { $gte: 5 } } }])
db.orders.aggregate([{ $collStats: { storageStats: {} } }])

killOp aborts the operation, it does not roll back completed writes. An updateMany killed halfway leaves the documents it already touched changed.

The profiler

The profiler writes slow operations into a capped system.profile collection in the same database. It is the tool for "which query is killing us".

db.setProfilingLevel(1, { slowms: 100 })       // log ops over 100ms
db.setProfilingLevel(1, { sampleRate: 0.1 })   // sample 10% of slow ops
db.getProfilingStatus()
db.setProfilingLevel(0)                        // off
LevelRecords
0Nothing profiled. Slow ops still hit the mongod log
1Operations slower than slowms (default 100)
2Every operation. Expensive, short bursts only
// Slowest recent operations
db.system.profile.find({ millis: { $gt: 200 } })
  .sort({ ts: -1 }).limit(10)
  .forEach(o => print(o.millis, o.op, o.ns, JSON.stringify(o.command).slice(0, 120)))

// Anything that scanned far more than it returned
db.system.profile.find({ $expr: { $gt: ["$docsExamined",
                                        { $multiply: ["$nreturned", 100] }] } })

// Which collections dominate
db.system.profile.aggregate([
  { $group: { _id: "$ns", n: { $sum: 1 }, avg_ms: { $avg: "$millis" } } },
  { $sort: { avg_ms: -1 } }
])

system.profile is capped at 1MB by default, so it wraps fast at level 2. planSummary on each entry tells you COLLSCAN or IXSCAN without rerunning explain. Level 2 on a busy production node is a self-inflicted outage, so use level 1 with a sampleRate.

Backup, restore, and data transfer

mongodump and mongoexport are different tools for different jobs. Dump is BSON and lossless, export is JSON or CSV and lossy.

# Whole deployment to one compressed archive
mongodump --uri="$URI" --archive=shop.gz --gzip

# Selective, and point-in-time consistent on a replica set
mongodump --uri="$URI" --archive=shop.gz --gzip --oplog \
  --nsInclude="shop.*" --nsExclude="shop.sessions"

# One collection, filtered
mongodump --uri="$URI" --db=shop --collection=orders \
  --query='{"status":"paid"}' --out=./dump

# Restore, dropping targets first
mongorestore --uri="$URI" --archive=shop.gz --gzip --drop --oplogReplay

# Rename on the way in
mongorestore --uri="$URI" --archive=shop.gz --gzip \
  --nsFrom="shop.*" --nsTo="shop_staging.*"

# Copy between clusters with no file on disk
mongodump --uri="$SRC" --archive --gzip | mongorestore --uri="$DST" --archive --gzip
mongoexport --uri="$URI" --collection=orders --type=csv \
  --fields=ref,status,total --out=orders.csv
mongoexport --uri="$URI" --collection=orders --jsonArray \
  --query='{"status":"paid"}' --out=orders.json

mongoimport --uri="$URI" --collection=orders --type=csv --headerline \
  --file=orders.csv
mongoimport --uri="$URI" --collection=orders --mode=upsert \
  --upsertFields=ref --file=orders.json
mongoimport --uri="$URI" --collection=orders --jsonArray \
  --numInsertionWorkers=4 --file=orders.json
mongoimport --modeDoes
insertDefault. Fails on a duplicate _id
upsertReplace matching documents, insert the rest
mergeMerge fields into matching documents, insert the rest
deleteDelete matching documents, ignore the rest

--upsertFields picks the match keys, defaulting to _id. --archive with no value means stdout or stdin, which is what makes the pipe above work. These tools ship separately as the MongoDB Database Tools, not with the server. For anything large, mongodump is a logical backup and will be slow to restore, so use filesystem snapshots or a managed backup for real disaster recovery and keep mongodump for moving data around.

Users and roles

Authentication data lives in a database, and authSource in the connection string names it. Create application users in admin and grant them roles scoped to their data database.

use admin
db.createUser({
  user: "app",
  pwd: passwordPrompt(),                  // never inline the password
  roles: [ { role: "readWrite", db: "shop" },
           { role: "read",      db: "analytics" } ]
})

db.grantRolesToUser("app", [ { role: "dbAdmin", db: "shop" } ])
db.revokeRolesFromUser("app", [ { role: "read", db: "analytics" } ])
db.changeUserPassword("app", passwordPrompt())
db.updateUser("app", { roles: [ { role: "read", db: "shop" } ] })
db.getUser("app", { showPrivileges: true })
db.dropUser("app")
show users

// Least privilege, custom role
db.createRole({
  role: "ordersReader",
  privileges: [ { resource: { db: "shop", collection: "orders" },
                  actions: ["find"] } ],
  roles: []
})
Built-in roleScopeGrants
readOne dbfind, and read of indexes and stats
readWriteOne dbread plus insert, update, delete, index creation
dbAdminOne dbIndexes, stats, validation, profiling. No data reads
userAdminOne dbCreate users and roles in that db. Effectively a privilege escalation path
dbOwnerOne dbreadWrite + dbAdmin + userAdmin
clusterMonitoradminRead-only monitoring, the right role for an exporter
clusterManager · hostManager · clusterAdminadminReplica set and shard operations
readAnyDatabase · readWriteAnyDatabaseadminThe same, across every database
backup · restoreadminWhat mongodump and mongorestore need
rootadminEverything. Not for an application
MechanismNotes
SCRAM-SHA-256Default since 4.0. Username and password
SCRAM-SHA-1Legacy, still negotiated for old drivers
MONGODB-X509Client certificate, no shared secret. The subject DN is the username
MONGODB-OIDCWorkload and workforce identity (7.0+)
MONGODB-AWSIAM credentials, Atlas
Kerberos · LDAPEnterprise only

?authSource=admin in the URI is the fix for the overwhelming majority of "auth failed" reports. The user exists, you are just looking for it in the wrong database.

Replica sets and read preferences

rs.initiate()                             // one-node set, for local dev
rs.status()                               // members, state, lag, election info
rs.conf()                                 // the configuration document
rs.add("db2.internal:27017")
rs.add({ host: "db3:27017", priority: 0, hidden: true })   // analytics node
rs.remove("db3:27017")
rs.stepDown(60)                           // yield PRIMARY for 60s, triggers election
rs.hello()                                // replaces the old isMaster
rs.printReplicationInfo()                 // oplog size and the time window it covers
rs.printSecondaryReplicationInfo()        // per-secondary lag
Read preferenceReads fromUse for
primaryPrimary only. Errors if there is noneDefault. Anything read-after-write
primaryPreferredPrimary, secondaries during a failoverAvailability over freshness
secondarySecondaries onlyAnalytics and reporting
secondaryPreferredSecondaries, primary as a fallbackOffloading reads
nearestLowest latency member, either roleGeographically spread readers
db.getMongo().setReadPref("secondaryPreferred")
db.orders.find().readPref("nearest", [{ region: "eu" }])   // with a tag set

Secondary reads are eventually consistent. Your own write may not be visible yet, so never mix a secondary read into a read-your-writes flow. Add maxStalenessSeconds (minimum 90) to exclude badly lagging members. The oplog (local.oplog.rs) is a capped collection, and rs.printReplicationInfo() shows the time window it holds. That window is the hard limit on how long a secondary can be offline before it needs a full resync, and on how far back a change stream can resume.

Change streams

A change stream is a tailable cursor over the oplog, filtered through an aggregation pipeline. It needs a replica set or sharded cluster, and it is the correct replacement for polling a collection.

const cs = db.orders.watch(
  [ { $match: { operationType: { $in: ["insert", "update"] },
                "fullDocument.status": "paid" } },
    { $project: { _id: 1, operationType: 1, "fullDocument.ref": 1 } } ],
  { fullDocument: "updateLookup" }
);

while (!cs.isClosed()) {
  if (cs.hasNext()) {
    const change = cs.next();
    printjson(change);
    saveToken(change._id);          // the resume token
  }
}
cs.close();
OptionDoes
fullDocument: "default"Update events carry only updateDescription, not the document
fullDocument: "updateLookup"Look the document up now, so it may already be newer than the event
fullDocument: "whenAvailable" / "required"Post-image from stored images (6.0+, needs changeStreamPreAndPostImages)
fullDocumentBeforeChangePre-image (6.0+, same collection setting)
resumeAfter: <token>Resume after a token you stored
startAfter: <token>Resume after an invalidate, which resumeAfter cannot do
startAtOperationTime: <ts>Start from a cluster timestamp
showExpandedEvents: trueInclude DDL events such as create and modify (6.0+)
maxAwaitTimeMSHow long the server waits before returning empty
Watch levelScope
db.orders.watch()One collection
db.watch()Every collection in a database
db.getMongo().watch()The whole deployment

Event operationType values: insert, update, replace, delete, drop, rename, dropDatabase, invalidate. Persist the resume token after every processed event, because that token is the only way to restart without gaps or replays. Resuming works only while the token is still inside the oplog window, so a consumer down longer than the oplog covers has to fall back to a full resync. updateLookup is a second read, not a snapshot, and it is why an event can report a status you never wrote in that update.

Sharding and shard keys

sh.status()
sh.enableSharding("shop")
sh.shardCollection("shop.orders", { customer_id: "hashed" })
sh.shardCollection("shop.events", { tenant_id: 1, created_at: 1 })

db.adminCommand({ refineCollectionShardKey: "shop.orders",
                  key: { customer_id: 1, ref: 1 } })       // 4.4+, suffix only
db.adminCommand({ reshardCollection: "shop.orders",
                  key: { tenant_id: 1, created_at: 1 } })  // 5.0+, full rewrite
sh.balancerStatus()
sh.stopBalancer()
sh.moveCollection("shop.settings", "shard02")     // 8.0+, unsharded collections
db.adminCommand({ unshardCollection: "shop.orders" })      // 8.0+

The shard key is the one decision that is expensive to get wrong, and here is what each choice costs you:

Shard keyBuysCosts
{ _id: "hashed" }Perfectly even writesEvery query without _id is a scatter-gather to all shards
Monotonic, e.g. { created_at: 1 }Range queries on timeHot shard. Every insert lands on the one holding the top range
High-cardinality field, e.g. { customer_id: 1 }Targeted per-customer queriesUneven if a few customers dominate
Compound, e.g. { tenant_id: 1, created_at: 1 }Targeted per tenant, range scans inside a tenantA single huge tenant cannot be split further
Low cardinality, e.g. { country: 1 }NothingJumbo, indivisible ranges. The classic mistake
ConstraintDetail
Queries without the shard keyBroadcast to every shard, and mongos merges. Latency becomes the slowest shard
Unique indexesMust be prefixed by the shard key. There is no cluster-wide unique index otherwise
Sort without the shard key prefixMerge sort across shards, done in mongos
Shard key valuesUpdatable since 4.2 inside a transaction or retryable write. _id never
Changing the keyAdd a suffix with refineCollectionShardKey (4.4+), or rewrite with reshardCollection (5.0+), which copies the whole collection
Multikey and array fieldsCannot be a shard key
BalancingBy data size since 6.0, not by chunk count

Shard only when one replica set genuinely cannot hold the working set or the write throughput. Sharding adds mongos, config servers, cross-shard latency, and a class of performance problem that does not exist on a replica set. Vertical growth and good indexes get further than most teams expect.

The 16MB limit and GridFS

Every BSON document is capped at 16MB, no exceptions and no configuration. That limit is a schema signal: an unbounded array in a document is a design bug that will eventually hit it.

SituationDo this
An array that grows forever (events, comments, readings)Move to one document per entry, or bucket a fixed number per document
A large binary blobObject storage, keep a URL in the document
A large file you must keep in MongoDBGridFS
High-volume timestamped metricsA time series collection (5.0+)

GridFS splits a file across two collections, fs.files for metadata and fs.chunks for 255KB pieces, and reassembles on read.

mongofiles --uri="$URI" --db=media put report.pdf
mongofiles --uri="$URI" --db=media list
mongofiles --uri="$URI" --db=media get report.pdf
db.fs.files.find({ filename: "report.pdf" })
db.fs.chunks.countDocuments({ files_id: fileId })

GridFS gives you no CDN, no range-request server, and no transformation pipeline, and every read is a multi-document assembly. It earns its place when you need one backup and one access-control story covering data and files together. For anything user-facing, object storage plus a URL is the simpler and faster answer.