MongoDB has a 16MB limit for individual BSON documents. This error occurs when trying to insert or update a document that exceeds this size limit. Common causes include storing large binary data, deeply nested objects, or accumulating too much data in arrays.
The "BSONObjectTooLarge: object to insert too large" error occurs when MongoDB rejects a document insertion or update because the document exceeds MongoDB's maximum BSON document size of 16 megabytes (16,777,216 bytes). BSON (Binary JSON) is the binary-encoded serialization format MongoDB uses to store documents. This limit exists because: 1. Larger documents consume more memory during processing 2. They increase network transfer times 3. They impact query performance and indexing efficiency 4. They affect replication and sharding operations When you encounter this error, MongoDB is protecting your database from performance degradation by enforcing this architectural limit. The error typically appears during insert(), update(), or save() operations when the serialized size of your document exceeds the limit.
Use MongoDB drivers or utilities to check document size before attempting to insert:
// Node.js example with MongoDB driver
const { MongoClient, BSON } = require('mongodb');
async function checkDocumentSize(document) {
// Calculate BSON size
const size = BSON.calculateObjectSize(document);
console.log(`Document size: ${size} bytes`);
if (size > 16 * 1024 * 1024) { // 16MB in bytes
console.error('Document exceeds 16MB limit!');
return false;
}
return true;
}
// Or using the driver's built-in check
const client = new MongoClient(uri);
const collection = client.db('mydb').collection('mycollection');
try {
await collection.insertOne(largeDocument);
} catch (error) {
if (error.message.includes('BSONObjectTooLarge')) {
console.log('Document too large - implement size check');
}
}For files, images, or binary data larger than 16MB, use MongoDB GridFS instead of storing directly in documents:
const { MongoClient } = require('mongodb');
const fs = require('fs');
async function storeLargeFile() {
const client = new MongoClient(uri);
const db = client.db('mydb');
// Create GridFS bucket
const bucket = new GridFSBucket(db, { bucketName: 'files' });
// Upload large file
const readStream = fs.createReadStream('large-video.mp4');
const uploadStream = bucket.openUploadStream('large-video.mp4');
readStream.pipe(uploadStream);
uploadStream.on('error', (error) => {
console.error('Upload failed:', error);
});
uploadStream.on('finish', () => {
console.log('File uploaded successfully with ID:', uploadStream.id);
// Store only the file reference in your document
const document = {
title: 'Video metadata',
fileId: uploadStream.id,
uploadedAt: new Date()
};
// This document will be small and within limits
await db.collection('videos').insertOne(document);
});
}Break large documents into smaller, related documents using references:
// Instead of one huge document:
// BAD: Single document with unlimited array
const badDocument = {
userId: '123',
activities: [/* thousands of activity objects */],
// ... other fields
};
// GOOD: Separate collections with references
const userDocument = {
userId: '123',
profile: { /* user profile data */ },
// Keep only recent activities or summary
recentActivities: [/* last 100 activities */],
totalActivities: 1500
};
// Activities in separate collection
const activityDocument = {
userId: '123',
activityType: 'login',
timestamp: new Date(),
details: { /* activity details */ }
};
// Query with joins when needed
const activities = await db.collection('activities')
.find({ userId: '123' })
.sort({ timestamp: -1 })
.limit(100)
.toArray();For documents with growing arrays, implement pagination or archival:
// Strategy 1: Paginate array data
const paginatedDocument = {
userId: '123',
// Store array in chunks
messagesPage1: [/* messages 1-100 */],
messagesPage2: [/* messages 101-200 */],
currentPage: 2,
totalMessages: 250
};
// Strategy 2: Archive old data
async function archiveOldMessages(userId) {
const user = await db.collection('users').findOne({ userId });
if (user.messages.length > 1000) {
// Move old messages to archive collection
const oldMessages = user.messages.slice(0, 500);
await db.collection('messageArchive').insertOne({
userId,
messages: oldMessages,
archivedAt: new Date()
});
// Keep only recent messages
await db.collection('users').updateOne(
{ userId },
{ $set: { messages: user.messages.slice(500) } }
);
}
}
// Strategy 3: Use capped arrays
const schema = {
userId: String,
// Keep only last 1000 messages
recentMessages: {
type: [MessageSchema],
maxCount: 1000
}
};When aggregation pipelines create large intermediate documents, break them into stages:
// Problematic: Single stage creating huge document
const badPipeline = [
{
$group: {
_id: '$userId',
allData: { $push: '$$ROOT' } // Pushes entire documents
}
}
];
// Better: Process in stages with limits
const optimizedPipeline = [
// Stage 1: Filter and project only needed fields
{
$match: { timestamp: { $gte: startDate } }
},
{
$project: {
userId: 1,
importantField: 1,
// Don't include large fields
largeBinaryField: 0
}
},
// Stage 2: Group with aggregation, not array accumulation
{
$group: {
_id: '$userId',
count: { $sum: 1 },
total: { $sum: '$amount' },
// Use $addToSet for unique values instead of $push for all
uniqueItems: { $addToSet: '$itemId' }
}
},
// Stage 3: Add pagination with $skip and $limit
{ $skip: 0 },
{ $limit: 100 }
];
// For very large aggregations, consider:
// 1. Using $facet for parallel processing
// 2. Implementing map-reduce for distributed processing
// 3. Using $out to write intermediate resultsImplement monitoring to catch size issues before they cause errors:
// Regular document size checks
async function monitorDocumentSizes() {
const collections = await db.listCollections().toArray();
for (const collInfo of collections) {
const collection = db.collection(collInfo.name);
// Sample documents to check sizes
const sample = await collection.find().limit(10).toArray();
sample.forEach((doc, index) => {
const size = JSON.stringify(doc).length;
if (size > 10 * 1024 * 1024) { // Warn at 10MB
console.warn(`Large document in ${collInfo.name}: ${size} bytes`);
}
});
// Check for documents approaching limit
const largeDocs = await collection.aggregate([
{
$addFields: {
docSize: { $bsonSize: '$$ROOT' }
}
},
{
$match: {
docSize: { $gt: 15 * 1024 * 1024 } // > 15MB
}
}
]).toArray();
if (largeDocs.length > 0) {
console.error(`Found ${largeDocs.length} documents approaching 16MB limit in ${collInfo.name}`);
}
}
}
// Set up alerts
const alertThreshold = 15 * 1024 * 1024; // 15MB
if (documentSize > alertThreshold) {
sendAlert('Document approaching BSON size limit', {
collection: collectionName,
documentId: doc._id,
size: documentSize
});
}## Understanding BSON Size Calculation
The 16MB limit applies to the serialized BSON size, not the JavaScript object size. Key factors affecting BSON size:
1. Field names: Each field name is stored as a string in BSON
- { "veryLongFieldNameThatTakesSpace": value } uses more space than { "v": value }
2. Data types: Some types have overhead:
- ObjectId: 12 bytes
- Date: 8 bytes
- Binary data: length + subtype + bytes
- Arrays: Additional array overhead
3. Nesting overhead: Each nested document adds BSON document overhead
## GridFS vs External Storage
While GridFS solves the 16MB limit by chunking files, consider:
- GridFS: Good for files up to 16GB, integrated with MongoDB, supports range queries
- External storage (S3, Azure Blob): Better for very large files, cheaper storage, CDN integration
## Sharding Considerations
For sharded collections:
- The 16MB limit applies per document, not per shard
- Large documents can't be split across shards
- Consider sharding key selection for documents that might grow large
## Compression Options
MongoDB 4.2+ supports snappy and zlib compression:
- Reduces storage size but not BSON limit
- Network transmission uses compressed BSON
- Enable with storage.wiredTiger.collectionConfig.blockCompressor
## Migration Strategies
If you have existing documents near or exceeding the limit:
1. Use $out aggregation to rewrite with normalized structure
2. Implement gradual migration during low-traffic periods
3. Use change streams to migrate documents as they're updated
## Related Errors
- DocumentTooLarge: Similar error in some MongoDB drivers
- QueryExceededMemoryLimitNoDiskUseAllowed: Aggregation memory limits
- TransactionTooLarge: Transaction operation size limits
StaleShardVersion: shard version mismatch
How to fix "StaleShardVersion: shard version mismatch" in MongoDB
MongoOperationTimeoutError: Operation timed out
How to fix "MongoOperationTimeoutError: Operation timed out" in MongoDB
MongoServerError: PlanExecutor error during aggregation :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. Aborting operation.
How to fix "QueryExceededMemoryLimitNoDiskUseAllowed" in MongoDB
MissingSchemaError: Schema hasn't been registered for model
How to fix "MissingSchemaError: Schema hasn't been registered for model" in MongoDB/Mongoose
CastError: Cast to ObjectId failed for value "abc123" at path "_id"
How to fix "CastError: Cast to ObjectId failed" in MongoDB