MongoDB Database Profiler
Learn to monitor MongoDB performance using the database profiler. This tutorial explains what the profiler is, how to enable it, how to read profiler output, and how to use it to find slow queries and improve performance.
Focus: monitor mongodb performance with database profiler
Your MongoDB cluster is humming along in production when a user reports that a critical endpoint is crawling. You check CPU, memory, and disk — all fine. The database is the bottleneck, but you have no idea which query is the culprit. The MongoDB database profiler is the built-in tool that records query execution details, including response times, so you can pinpoint slow operations and fix them before they become outages. This lesson shows you exactly how to monitor MongoDB performance with the database profiler — from enabling it to reading the output and acting on what you find.
The problem this lesson solves
Debugging performance issues in MongoDB without profiling is like searching for a black cat in a dark room. You might guess, but you'll waste time and risk making things worse. Common pain points include:
- Slow queries that degrade user experience but only appear under load.
- Inefficient indexes that aren't used, causing full collection scans.
- Memory pressure from operations that fetch too many documents.
- No visibility into which operations are slow, when, and why.
Without the profiler, you're flying blind. The profiler gives you a system.profile collection that records operation metadata like millis (execution time), nreturned (documents returned), and ns (namespace). With this data, you can move from guessing to evidence-based optimization.
Core concept / mental model
Think of the database profiler as MongoDB's built-in performance recorder — like a flight data recorder for your database. It doesn't fix problems; it tells you what happened, when, and how long it took. When enabled, MongoDB writes profiler records for operations that match your threshold into a capped collection named system.profile inside the database.
Profiling levels:
| Level | Behavior |
|---|---|
| 0 | Profiling off (default) |
| 1 | Record operations slower than a threshold (default 100ms) |
| 2 | Record all operations |
The profiler can be scoped at three levels: instance (via mongod config), database, or per-operation using db.setProfilingLevel(). The database-level setting is the most common for targeted monitoring.
Key definitions:
- Profiling level: 0, 1, or 2 as above.
- Slowms: the threshold in milliseconds for level 1.
- system.profile: the capped collection storing profiler records (default 1MB).
- Operation types: query, insert, update, remove, command.
In this mental model, your job is to enable the recorder, read the tape, and optimize the operations that stand out.
How it works step by step
-
Enable profiling on the target database.
javascript db.setProfilingLevel(1, { slowms: 200 })This records operations slower than 200ms. Level 2 records everything — use sparingly in production. -
Verify the level using
db.getProfilingStatus(). -
Run your application or workload for a period to capture real operations.
-
Query the
system.profilecollection — it's a normal capped collection, so you can query it like any other. -
Analyze results: look for slow
millis, highnreturned, largedocsExamined, and missing indexes. -
Optimize by adding indexes, adjusting query patterns, or rewriting operations.
-
Adjust profiling — raise
slowmsor disable profiling when not needed to reduce overhead (profiling itself adds a small cost).
Hands-on walkthrough
Let's apply this to a real scenario. Assume we have a users collection with 1 million documents and a query that's timing out.
1. Enable profiling and check status
// Connect to your database (e.g., 'appdb')
use appdb
db.setProfilingLevel(1, { slowms: 100 })
db.getProfilingStatus()
// Expected output: { was: 1, slowms: 100, sampleRate: 1 }
2. Run a slow query
// Simulate a slow query on an unindexed field
const start = new Date()
db.users.find({ email: "alice@example.com" }).toArray()
const elapsed = new Date() - start
print(`Query took ${elapsed} ms`)
// Output: Query took 175 ms (or similar)
3. Read the profiler record
db.system.profile.find(
{ ns: "appdb.users", op: "query" },
{ ns: 1, millis: 1, nreturned: 1, docsExamined: 1, ts: 1 }
).sort({ millis: -1 }).limit(5).toArray()
Output (simplified):
[
{
_id: ...,
ns: "appdb.users",
millis: 175,
nreturned: 1,
docsExamined: 1000000,
ts: ISODate("2025-05-06T10:00:00Z")
}
]
The docsExamined is a million — a full collection scan. nreturned is 1. That's the smoking gun.
4. Fix with an index and re-check
// Create an index on email
use appdb
db.users.createIndex({ email: 1 })
// Re-run the same query
const start = new Date()
db.users.find({ email: "alice@example.com" }).toArray()
const elapsed = new Date() - start
print(`Query took ${elapsed} ms`)
// Query the profiler again to confirm improvement
// Expect millis to drop drastically, docsExamined to be 1
After the index, the query should return in a few milliseconds with docsExamined: 1.
5. Keep only valuable profiler data
// Clear old profile entries (as needed – you may want to keep recent ones)
db.system.profile.drop()
// Re-create the collection as capped? Dropping and re-running setProfilingLevel will recreate it.
Pro tip: Instead of dropping the whole collection, use
db.system.profile.find().sort({$natural:-1}).limit(100)to inspect recent records and archive older ones manually.
Compare options / when to choose what
| Option | Strengths | Weaknesses | Best for |
|---|---|---|---|
| Database profiler | Built-in, zero setup, captures actual executed operations | Overhead, hard to correlate with app context | Production troubleshooting, ad-hoc analysis |
| MongoDB Atlas / Compass Profiler | Visual charts, integrated UI, real-time alerts | Not available in self-managed setups, extra cost in Atlas | Atlas users, teams needing dashboards |
| mongostat | Real-time stats, lightweight | No query details, only counters | Quick health checks, capacity planning |
| mongotop | Shows read/write time per collection | No per-operation detail | Identifying hot collections |
| third-party tools (PMM, Datadog) | Rich dashboards, historical data | Requires agents, learning curve | Large enterprises with observability stacks |
Choose the database profiler when you need to see exactly what ran, how long it took, and how many documents were scanned — especially on self-managed MongoDB. For continuous monitoring in production, consider a real-time tool like mongostat combined with periodic profiler dumps.
Troubleshooting & edge cases
-
Profiler not recording anything: Check the profiling level and
slowms. Level 0 disables it. Make sure you're connected to the right database —use <db>matters. -
system.profiledoesn't exist: It's created automatically when you enable profiling. If you drop it, you must callsetProfilingLevelagain to recreate it as capped. -
Profiler overhead too high: Level 2 in production can slow things down. Use level 1 with a reasonable
slowms(e.g., 100–200 ms), or usesampleRate(as of MongoDB 4.2) to capture only a percentage of slow operations. -
Capped collection fills up: The default size is 1 MB; once full, oldest entries are overwritten. If you need more history, change the size before enabling profiling: drop
system.profile, thendb.createCollection( "system.profile", { capped: true, size: 10485760 } )(10 MB). -
Cannot drop system.profile while profiling is on: Disable profiling first (
db.setProfilingLevel(0)), then drop or resize. -
Profiler shows
docsExaminedhuge butmillislow: Indexes help, but you might be scanning too many documents — review your query filter for selectivity.
What you learned & what's next
You've learned how to monitor MongoDB performance with the database profiler — enabling levels, setting slowms, querying system.profile, and interpreting key fields like millis, docsExamined, and nreturned. You can now identify slow queries, add indexes, and verify improvements. This is a core skill for any MongoDB administrator or developer. Next, you'll learn how to use the profiler's findings to design effective indexes — the step after identification is optimization.
Practice recap
Try enabling level 1 profiling on a test database, create an unindexed query that scans many documents, run it, and inspect the profiler record. Then add an index and re-run the query — compare the millis and docsExamined values in the profiler output to confirm the improvement.
Common mistakes
- Forgetting to
use <db>before setting profiling level — you end up profiling the wrong database. - Enabling level 2 in production without limiting time — overhead spikes and system.profile grows too fast.
- Ignoring
docsExamined— a query can be fast but still scan millions of documents, hurting cache and memory. - Not adjusting the capped collection size — you lose historical data before you analyze it.
- Leaving profiling on permanently at level 1 with a low slowms — adds continuous overhead.
Variations
- Use
sampleRate(MongoDB 4.2+) to record only a percentage of slow operations, reducing overhead. - Set profiling via the configuration file (
operationProfiling.mode) for a persistent, server-level setting. - Use Compass or Atlas profiler UI for visual analysis without writing queries.
Real-world use cases
- A production API endpoint shows intermittent latency; you enable profiling and find a slow, unindexed find query on a 50M-document collection.
- During a traffic spike, you use mongostat to spot a hot collection, then enable the profiler to capture the exact problematic update operations.
- After a schema change, you enable profiling to ensure background tasks (like nightly ETL) aren't exceeding your 1-second SLA.
Key takeaways
- The database profiler records operations in system.profile with details like millis, docsExamined, and nreturned.
- Level 0 is off, level 1 logs operations slower than a threshold, level 2 logs everything — use level 1 in production.
- Set a sensible slowms (e.g., 100–200 ms) and adjust the capped collection size to retain enough history.
- Always check docsExamined vs nreturned — a full collection scan is a red flag even if millis is low.
- Dropping system.profile or changing its size requires disabling profiling first.
- Profiling adds overhead; use it temporarily or with sampling to minimize impact.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.