What this usually means
The document you're trying to insert or update has grown beyond MongoDB's 16MB BSON document limit. This isn't a configurable threshold—it's a hard limit baked into the storage engine. The root cause is typically one of three patterns: an unbounded array that accumulates data (e.g., storing all user events in a single document), embedding large unstructured content (like base64-encoded files) directly instead of using GridFS, or aggressive use of $push and $addToSet on arrays without a cap. In replica sets, a secondary can hit this during oplog replay if the primary's write already exceeded the limit (though the primary should reject it first). I've also seen this in aggregation pipelines where $out tries to write a single document containing a large array of results. The fix is always structural: either move data to separate documents (normalization), use GridFS for binary blobs, or cap array growth with $slice or explicit limits.
The first ten minutes — establish facts before touching code.
- 1Run db.collection.find().forEach(doc => { if(Object.bsonsize(doc) > 16793600) print(doc._id); }) to identify oversized documents.
- 2Check the MongoDB logs: grep 'exceeds maximum' /var/log/mongodb/mongod.log
- 3Use db.collection.stats() to see avgObjSize and if any document is near 16MB.
- 4For GridFS, check fs.files metadata: db.fs.files.find({ length: { $gt: 16793600 } })
- 5If the error happens on a secondary, check replication lag and look for documents that are just under 16MB on the primary.
- 6Monitor oplog size: db.printReplicationInfo() to see if oplog entries themselves are near 16MB.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchApplication logs for the exact document _id and collection name from the error message.
- searchMongoDB server log: /var/log/mongodb/mongod.log for 'exceeds maximum' entries.
- searchMongoDB driver error stack trace—sometimes points to the specific field causing bloat.
- searchGridFS fs.files collection: check length field for files near 16MB.
- searchAggregation pipeline stages: examine $out, $merge, and $group that might produce large documents.
- searchReplica set secondary's oplog: db.oplog.rs.find({ ns: 'db.collection' }).sort({$natural:-1}).limit(1).pretty() to see the last operation and its size.
- searchAtlas monitoring: check 'Max Document Size' metric in the cluster overview.
Practical causes, not theory. These are the things you will actually find.
- warningUnbounded array: using $push without a $slice or capped array, allowing arrays to grow indefinitely.
- warningEmbedding large binary data (images, PDFs) as base64 string directly in documents.
- warningAggregation $group stage that groups many documents into a single document with a large array.
- warningGridFS chunk size misconfiguration: if the chunk size is set too high (default 255KB), a file just under 16MB might produce a single chunk >16MB if the file is stored as one chunk.
- warningSubdocument bloat: deeply nested objects or repeated fields from denormalization.
- warningMigrating from relational databases without considering document size limits.
Concrete fix directions. Pick the one that matches your root cause.
- buildNormalize large arrays: move array data into a separate collection with a reference back to the parent document.
- buildUse GridFS for binary data: store files in GridFS, not as base64 in documents.
- buildCap arrays with $slice on update: db.collection.updateOne({ _id: X }, { $push: { arr: { $each: [newItem], $slice: -100 } } })
- buildUse $out or $merge with explicit limits in aggregation: add a $limit stage before $out to prevent oversized output.
- buildUse a background validator to monitor document sizes and alert when approaching 14MB.
- buildShard the collection to distribute large documents across shards (if the document itself is still under 16MB but you expect growth).
A fix you cannot prove is a guess. Close the loop.
- verifiedAfter fix, attempt the write operation that previously failed—should succeed.
- verifiedRun Object.bsonsize() on the fixed document to confirm it's under 16MB.
- verifiedCheck application logs for no further 'exceeds maximum size' errors.
- verifiedFor replication: verify secondary's oplog entries are under 16MB and no lag.
- verifiedFor GridFS: upload a file just under 16MB and verify it splits into multiple chunks.
- verifiedMonitor MongoDB logs for 24 hours to ensure no recurrence.
Things that make this bug worse or harder to find.
- warningTrying to change the 16MB limit—it's hardcoded and not configurable.
- warningIgnoring the error and retrying: the document will keep failing until the data structure changes.
- warningUsing GridFS for everything: if a document is just a bit over 16MB, first check if you can split the data into separate documents.
- warningAssuming the error is always from the application—check secondary oplog as well.
- warningUsing $push with $each without $slice: this is the most common cause of bloat.
- warningNot monitoring document sizes proactively: set up alerts at 14MB.
User Profile Document Grows Past 16MB Due to Event Log Array
Timeline
- 09:15Alert: PagerDuty notification for 'MongoDB write error' on production UserProfiles collection.
- 09:17Checked application logs: 'MongoError: document exceeds maximum size' on user_id=abc123.
- 09:20Ran db.userprofiles.find({_id: ObjectId('abc123')}).pretty() - document returned but output truncated in terminal.
- 09:25Queried Object.bsonsize on the document: 21.4MB.
- 09:30Examined document structure: found array 'event_log' with 1.2 million entries.
- 09:35Checked code: user profile update uses $push to append event_log without $slice.
- 09:40Decided to migrate event_log to a separate 'UserEvents' collection.
- 09:50Backfilled existing events to new collection and removed event_log array from user profile.
- 10:00Updated application code to insert events into UserEvents and fetch via reference.
- 10:15Deployed fix; write succeeded.
- 10:20Monitored logs for 30 minutes: no further errors.
I was on-call when PagerDuty alerted that UserProfiles collection writes were failing. The error was straightforward: 'document exceeds maximum size' for a specific user. I first checked the application logs and found the exact user_id. Then I connected to the MongoDB primary and tried to find the document, but my terminal output was cut off—that's a hint the document is huge.
I used Object.bsonsize() to confirm it was 21.4MB. Scanning the document structure, I saw an 'event_log' array with over a million entries. I remembered that months ago we added event tracking to user profiles using $push without any cap. That was the culprit. The team had debated storing events separately but never prioritized it.
I immediately migrated the event_log to a new UserEvents collection. I wrote a script to backfill the existing events, then removed the array from the user profile. I updated the service to insert events into the new collection and fetch them on demand. After deploying, the write succeeded. The lesson: always cap arrays or normalize when there's a risk of unbounded growth. We also added a cron job to monitor document sizes weekly.
Root cause
Unbounded $push on 'event_log' array in user profile document caused it to exceed 16MB.
The fix
Normalized event_log into a separate UserEvents collection; updated application code to use references.
The lesson
Never allow arrays to grow without a limit. Use $slice or move to a separate collection.
MongoDB documents are stored in BSON format, which includes a 32-bit integer for the total document size. That gives a maximum of 2^32-1 bytes, but MongoDB deliberately caps it at 16MB (16793600 bytes) to ensure decent network performance and memory usage. This limit is per document, not per collection or database.
The error is thrown by the server when it receives a write operation with a BSON object larger than 16MB. The driver serializes the document and compares its size to the limit. If you're using GridFS, each chunk is limited to the chunk size (default 255KB) but the metadata document in fs.files must also be under 16MB.
The quickest way to find oversized documents is to use Object.bsonsize() in the mongo shell. Loop through all documents and compare: db.collection.find().forEach(doc => { var size = Object.bsonsize(doc); if(size > 16793600) print(doc._id + ': ' + size); }). This can be slow on large collections, so you may want to add a limit or run during off-peak.
If you suspect a specific field is causing bloat, measure subdocuments: Object.bsonsize(doc.largeField). This helps pinpoint the exact field. For example, if you have a 'data' field that stores base64, you'll see it's the bulk of the size.
A common misconception is that GridFS avoids the 16MB limit by splitting files into chunks. While it does, the fs.files collection still has a document per file that contains metadata (filename, length, uploadDate, etc.). That metadata document must be under 16MB. In practice, this is rarely an issue unless you store large custom metadata.
However, if your application writes a file that is exactly 16MB and the chunk size is set to 16MB (which is not the default), the single chunk could exceed the limit. The default chunk size is 255KB, so a 16MB file splits into ~64 chunks, each well under the limit.
Aggregation pipelines can produce documents that exceed 16MB in the $out, $merge, or $group stages. For example, a $group that uses $push to accumulate all documents into one array can easily exceed 16MB with enough input. The error will appear when the pipeline execution tries to write the result.
To debug, run the pipeline stage by stage, or add a $limit stage early to reduce the data. You can also use $bucket or $facet to partition the data. Check the output of $group: if it's producing large arrays, consider normalizing the output or using $project to trim fields.
Set up a monitoring script that runs daily to check for documents approaching the limit. Use a threshold of 14MB to allow headroom. You can use MongoDB's aggregation framework with $bsonSize (available in 4.4+): db.collection.aggregate([ { $match: { $expr: { $gt: [ { $bsonSize: '$$ROOT' }, 14000000 ] } } } ]).
Implement application-level validation: before writing, calculate the document size server-side using the driver's serialization method. For Node.js, you can use BSON.calculateObjectSize(doc) and reject if >16MB. Also, enforce array caps in schema validation: $jsonSchema can limit array lengths using maxItems.
Frequently asked questions
Can I increase the 16MB limit?
No, the 16MB limit is hardcoded into MongoDB's BSON implementation. It's not configurable. You must change your data model to stay under the limit. Use GridFS for binary files or normalize your schema.
Does the 16MB limit apply to the oplog?
Yes, oplog entries are also BSON documents and must be under 16MB. If a write operation produces an oplog entry larger than 16MB (e.g., a multi-update that modifies many documents), it will fail. This is rare but can happen with multi:true updates on large datasets.
How do I fix a document that is already over 16MB?
You cannot update it because any write will fail. The document must be deleted and re-inserted with a smaller structure, or you can use the aggregation pipeline with $out to a new collection while filtering out large fields. In extreme cases, you may need to manually reconstruct the document from the database files.
Does sharding help with the 16MB limit?
Sharding distributes documents across shards, but each individual document must still be under 16MB. Sharding does not increase the per-document limit. It only helps with horizontal scaling of data volume.
What is the difference between 16MB limit and GridFS chunk size?
The 16MB limit is for a single BSON document. GridFS splits files into chunks (default 255KB each) and stores them as separate documents in fs.chunks. The metadata in fs.files is a single document and must be under 16MB. So the file itself can be larger than 16MB, but its metadata cannot.