What it means
maxTimeMS is a time limit, in milliseconds, that a client attaches to an operation. The server
checks it while the operation runs, and when the time is up it stops the operation and returns code
50, MaxTimeMSExpired, with operation exceeded time limit. The limit counts the server’s
processing time, added up across a cursor’s batches; network time and the time a cursor sits idle
between batches don’t count.
The text before caused by says which operation it was: Executor error during find command: <db>.<collection> for a find, PlanExecutor error during aggregation for a pipeline. On its own
(operation exceeded time limit) it often means the operation was waiting, for example for a lock
held by a transaction.
Where the limit comes from:
- Your code:
.maxTimeMS(…)on a cursor,{ maxTimeMS: … }on an aggregate or a write. - Your ODM or framework: Mongoose’s
maxTimeMS()query option, or a default set in a library. - The server: on MongoDB 8.0 and later, the
defaultMaxTimeMScluster parameter sets a default for reads that don’t set their own.
Common causes
- A query without a useful index: a collection scan that took longer as the data grew.
- A heavy pipeline:
$lookupwithout an index on the foreign field,$groupor$sortover the whole collection, or a$regexthat can’t use an index. - Waiting for a lock: a write blocked by a document that an open transaction changed but hasn’t committed.
- A limit that’s too tight for an operation that is slow by nature, such as a report or an export.
- A busy or undersized server: everything is slower, and operations near their limit tip over.
How to fix it
See how long it takes, and why
Run it with explain("executionStats") (no time limit) and look at executionTimeMillis,
totalDocsExamined and the plan’s stages:
db.events.find({ kind: 3 }).explain("executionStats").executionStats
COLLSCAN with totalDocsExamined far above nReturned means an index would help.
Add the index the query needs
db.events.createIndex({ kind: 1, placedAt: -1 })
Fields compared for equality go first, then the sort fields, then ranges.
Look for what it’s waiting on
While it runs, db.currentOp() shows each operation, how long it has run (secs_running) and
whether it’s waiting for a lock (waitingForLock):
db.currentOp({ active: true, secs_running: { $gte: 1 } })
If writes wait on a transaction, find out why that transaction stays open, and keep transactions short.
Raise the limit only for slow-by-design work
db.events.aggregate(pipeline, { maxTimeMS: 120000 })
On MongoDB 8.0 and later, check whether the server sets a default for reads:
db.adminCommand({ getClusterParameter: "defaultMaxTimeMS" })
A maxTimeMS on the operation overrides it.
Reproduce it
MongoDB 8.0.32, mongosh 2.12.0, 5,000 documents and a filter that runs JavaScript ($where with a
10 ms sleep) to make the query slow:
db.events.find({ $where: "sleep(10); return true" }).maxTimeMS(100)
MongoServerError[MaxTimeMSExpired]: Executor error during find command: seo_err_mongo.events :: caused by :: operation exceeded time limit
The error’s code was 50 and its codeName MaxTimeMSExpired.
In a temporary single-member replica set of our own (mongo:8.0 --replSet rs0, removed
afterwards), one session updated a document in a transaction and didn’t commit. A plain
updateOne on the same document from another connection, with { maxTimeMS: 2000 }, waited and
then failed after 2,038 ms:
MongoServerError[MaxTimeMSExpired]: operation exceeded time limit
After the transaction committed, the same update went through. A query stopped from outside with
db.killOp() failed differently, with code 11601 (Interrupted):
operation was interrupted after 214463 ms.
In Inlet
Queries use mongosh syntax, so .maxTimeMS(…) and explain("executionStats") work in a query tab,
and a running query can be cancelled. When a query is refused or stopped, Inlet shows the server’s
message.