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
| Level | Records |
|---|---|
0 | Nothing profiled. Slow ops still hit the mongod log |
1 | Operations slower than slowms (default 100) |
2 | Every 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 --mode | Does |
|---|---|
insert | Default. Fails on a duplicate _id |
upsert | Replace matching documents, insert the rest |
merge | Merge fields into matching documents, insert the rest |
delete | Delete 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 role | Scope | Grants |
|---|---|---|
read | One db | find, and read of indexes and stats |
readWrite | One db | read plus insert, update, delete, index creation |
dbAdmin | One db | Indexes, stats, validation, profiling. No data reads |
userAdmin | One db | Create users and roles in that db. Effectively a privilege escalation path |
dbOwner | One db | readWrite + dbAdmin + userAdmin |
clusterMonitor | admin | Read-only monitoring, the right role for an exporter |
clusterManager · hostManager · clusterAdmin | admin | Replica set and shard operations |
readAnyDatabase · readWriteAnyDatabase | admin | The same, across every database |
backup · restore | admin | What mongodump and mongorestore need |
root | admin | Everything. Not for an application |
| Mechanism | Notes |
|---|---|
SCRAM-SHA-256 | Default since 4.0. Username and password |
SCRAM-SHA-1 | Legacy, still negotiated for old drivers |
MONGODB-X509 | Client certificate, no shared secret. The subject DN is the username |
MONGODB-OIDC | Workload and workforce identity (7.0+) |
MONGODB-AWS | IAM credentials, Atlas |
| Kerberos · LDAP | Enterprise 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 preference | Reads from | Use for |
|---|---|---|
primary | Primary only. Errors if there is none | Default. Anything read-after-write |
primaryPreferred | Primary, secondaries during a failover | Availability over freshness |
secondary | Secondaries only | Analytics and reporting |
secondaryPreferred | Secondaries, primary as a fallback | Offloading reads |
nearest | Lowest latency member, either role | Geographically 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();
| Option | Does |
|---|---|
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) |
fullDocumentBeforeChange | Pre-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: true | Include DDL events such as create and modify (6.0+) |
maxAwaitTimeMS | How long the server waits before returning empty |
| Watch level | Scope |
|---|---|
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.
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.
| Situation | Do 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 blob | Object storage, keep a URL in the document |
| A large file you must keep in MongoDB | GridFS |
| High-volume timestamped metrics | A 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.