Home / Databases / MongoDB

DatabasesCheat sheetIntermediate

MongoDB DBA cheat sheet: mongosh, currentOp, replica sets, indexes, users and backups

The mongosh commands a MongoDB DBA uses every week - server health, running operations, replica sets, sizes, indexes, profiler, users, sharding and mongodump/mongorestore.

Deepak Bhardwaj7 min read

Applies toMongoDB 6.0 / 7.0 / 8.0 self-managed, mongosh 2.x, MongoDB Database Tools 100.x; most commands also work on Atlas

How to use this sheet

Commands are for mongosh, the current MongoDB shell (the legacy mongo shell was removed in 6.0). Run admin commands against the admin database or with db.adminCommand(). On Atlas, some commands (for example shutdown, setParameter, logRotate) are not available to your user; use the Atlas UI or Admin API instead.

Connect

# Replica set, prompting for the password
mongosh "mongodb://db1:27017,db2:27017,db3:27017/?replicaSet=rs0" \
  --username dbadmin --authenticationDatabase admin

# Atlas / SRV with TLS
mongosh "mongodb+srv://cluster0.example.mongodb.net/" --username dbadmin

# Run one command and exit (good for scripts)
mongosh --quiet "mongodb://db1:27017/admin" -u dbadmin -p --eval 'db.hello().isWritablePrimary'

Server health

db.version()
db.hello()                                         // primary/secondary, set name, hosts
db.adminCommand({ getCmdLineOpts: 1 }).parsed      // effective config file settings
db.serverStatus().connections                      // current, available, totalCreated
db.serverStatus().opcounters                       // insert/query/update/delete/getmore/command
db.serverStatus().mem                              // resident and virtual MB
db.adminCommand({ getParameter: 1, featureCompatibilityVersion: 1 })

WiredTiger cache use:

const c = db.serverStatus().wiredTiger.cache;
print("used GB:  ", (c["bytes currently in the cache"] / 1024 ** 3).toFixed(1));
print("max GB:   ", (c["maximum bytes configured"]    / 1024 ** 3).toFixed(1));
print("dirty GB: ", (c["tracked dirty bytes in the cache"] / 1024 ** 3).toFixed(2));

From the OS shell, live counters every 5 seconds:

mongostat --uri "mongodb://dbadmin@db1:27017/?authSource=admin" 5
mongotop  --uri "mongodb://dbadmin@db1:27017/?authSource=admin" 5

What is running right now

// Operations running longer than 5 seconds
db.currentOp({ active: true, secs_running: { $gt: 5 } })

// Aggregation form: more filtering, includes other users' ops
db.getSiblingDB("admin").aggregate([
  { $currentOp: { allUsers: true, idleConnections: false } },
  { $match: { active: true, secs_running: { $gte: 5 } } },
  { $project: { opid: 1, secs_running: 1, op: 1, ns: 1, client: 1,
                "command.filter": 1, planSummary: 1, waitingForLock: 1 } },
  { $sort: { secs_running: -1 } }
])

// Index builds in progress
db.currentOp({ $or: [ { op: "command", "command.createIndexes": { $exists: true } },
                      { op: "none", msg: /^Index Build/ } ] })

db.killOp(12345)        // opid from the output above

Warning

Check ns, client and op before killOp. Killing an internal replication or balancer operation, or a long multi-document transaction near its commit, can do more harm than the slow query.

Replica sets

rs.status()                         // member state, health, optime, lag
rs.conf()                           // members, priorities, votes, hidden, delay
rs.printReplicationInfo()           // oplog size and time window on this member
rs.printSecondaryReplicationInfo()  // how far each secondary is behind the primary

rs.stepDown(60)                     // primary steps down, not re-electable for 60 s
rs.freeze(120)                      // run on a secondary: don't seek election for 120 s
rs.add({ host: "db4:27017", priority: 0, votes: 0 })
rs.remove("db4:27017")

Quick lag summary:

rs.status().members.map(m => ({ name: m.name, state: m.stateStr,
  lagSec: (rs.status().members.find(p => p.stateStr === "PRIMARY").optimeDate - m.optimeDate) / 1000 }))

Change a priority safely by editing the config object and reapplying it:

cfg = rs.conf();
cfg.members[2].priority = 0.5;
rs.reconfig(cfg);

Caution

rs.reconfig(cfg, { force: true }) is for losing a majority only. Forced reconfiguration can roll back writes and create divergent members. Follow the no-primary runbook before using it.

Sizes and storage

db.stats(1024 * 1024)                         // database sizes in MB
db.orders.totalSize()                          // data + indexes, bytes
db.orders.storageSize()
db.orders.totalIndexSize()
db.orders.estimatedDocumentCount()

// Every collection in the current database, biggest first
db.getCollectionNames().map(n => ({ n, mb: (db[n].totalSize() / 1048576).toFixed(0) }))
  .sort((a, b) => b.mb - a.mb).slice(0, 15)

// Free space reusable inside a collection (WiredTiger)
db.orders.aggregate([{ $collStats: { storageStats: {} } }])
  .next().storageStats.wiredTiger["block-manager"]["file bytes available for reuse"]

compact returns free space to the OS. Run it per collection and check the documentation for your version: since 4.4 it no longer blocks reads and writes on the collection, but it is resource-intensive.

db.runCommand({ compact: "orders" })

Indexes

db.orders.getIndexes()
db.orders.createIndex({ customerId: 1, status: 1, createdAt: -1 }, { name: "ix_cust_status_created" })
db.orders.hideIndex("ix_old")      // planner ignores it; test before dropping
db.orders.unhideIndex("ix_old")
db.orders.dropIndex("ix_old")

// Usage since the last restart, per index, on this member only
db.orders.aggregate([{ $indexStats: {} }, { $project: { name: 1, "accesses.ops": 1, "accesses.since": 1 } }])

Tip

$indexStats counters are per member and reset at restart. Check every data-bearing member, including secondaries that serve reads, and look at a period that covers month-end jobs before dropping an index. Hide it first; unhide is instant.

Slow queries: profiler and explain

db.setProfilingLevel(1, { slowms: 100 })   // log ops slower than 100 ms in this database
db.getProfilingStatus()

db.system.profile.find({ millis: { $gt: 100 } })
  .sort({ ts: -1 }).limit(10)
  .projection({ op: 1, ns: 1, millis: 1, planSummary: 1, docsExamined: 1, keysExamined: 1, nreturned: 1 })

db.setProfilingLevel(0)                    // off again; level 2 logs everything - avoid in production

db.orders.find({ customerId: 42, status: "open" }).sort({ createdAt: -1 })
  .explain("executionStats")

In explain, compare totalDocsExamined to nReturned and look for COLLSCAN or SORT stages. See the slow-query post for the ESR rule.

Users and roles

use admin
db.createUser({
  user: "app_orders",
  pwd: passwordPrompt(),
  roles: [ { role: "readWrite", db: "orders" } ]
})
db.getUsers()
db.getUser("app_orders", { showPrivileges: true })
db.grantRolesToUser("app_orders", [ { role: "read", db: "reporting" } ])
db.revokeRolesFromUser("app_orders", [ { role: "read", db: "reporting" } ])
db.changeUserPassword("app_orders", passwordPrompt())
db.dropUser("app_orders")

db.runCommand({ connectionStatus: 1 })    // who am I and what roles do I have

On Atlas, create database users through the UI, CLI (atlas dbusers create) or Admin API rather than db.createUser.

Logs and parameters

db.adminCommand({ getLog: "global" }).log.slice(-20)      // recent log lines (JSON)
db.setLogLevel(1, "query")                                 // more detail for one component
db.setLogLevel(0, "query")                                 // back to default
db.adminCommand({ logRotate: 1 })
db.adminCommand({ getParameter: 1, slowOpThresholdMs: 1 })

Sharded clusters (run on mongos)

sh.status()
sh.getBalancerState()
sh.isBalancerRunning()
sh.stopBalancer()      // before backups or maintenance
sh.startBalancer()
db.orders.getShardDistribution()

Backup and restore with the database tools

# Full replica set member dump with a consistent point via the oplog
mongodump --uri "mongodb://dbadmin@db2:27017/?authSource=admin&readPreference=secondary" \
  --oplog --gzip --archive=/backup/rs0_$(date +%F).archive.gz

# One database
mongodump --uri "mongodb://dbadmin@db2:27017/?authSource=admin" --db orders \
  --gzip --archive=/backup/orders_$(date +%F).archive.gz

# Test a restore without writing anything
mongorestore --uri "mongodb://dbadmin@restore01:27017/?authSource=admin" \
  --gzip --archive=/backup/rs0_2026-10-26.archive.gz --oplogReplay --dryRun

# Restore one database under a new name
mongorestore --uri "mongodb://dbadmin@restore01:27017/?authSource=admin" \
  --gzip --archive=/backup/orders_2026-10-26.archive.gz \
  --nsInclude "orders.*" --nsFrom "orders.*" --nsTo "orders_restore.*"

Caution

mongorestore --drop drops each collection before restoring it. Never point it at production by mistake: restore to a separate host or a renamed namespace first, then validate counts.

mongodump suits small and medium databases. For large ones, use filesystem snapshots with journaling on the same volume, Ops Manager / Cloud Manager, or Atlas cloud backups with point-in-time restore.

Upgrades: feature compatibility version

After every member runs the new binary and the cluster is stable:

db.adminCommand({ setFeatureCompatibilityVersion: "8.0", confirm: true })   // confirm required from 7.0

Set FCV only when you no longer need to downgrade the binaries.

Prevent surprises

  • Script rs.printReplicationInfo() and alert when the oplog window falls below your longest expected maintenance or outage (for example 24 hours).
  • Keep the profiler at level 1 with a sensible slowms in non-production and sample production with care.
  • Store these snippets in a shared .mongoshrc.js or snippet file so the whole team runs the same checks.
  • Test a mongorestore --dryRun and a real restore to a scratch host every quarter.