updateBatch - Update documents in batches
API parameter description
Each selected document is checked against the original business filter again when its write is dispatched. Documents that no longer match are skipped. On a fatal batch error, no new batch is acquired; already running batches finish before the error is returned. For strict Model updates, conflictCount refers to a version mismatch, while a changed filter or concurrent deletion is counted as skipped.
Method signature
collection(name: string).updateBatch(
filter: object,
update: object,
options?: UpdateBatchOptions
): Promise<UpdateBatchResult>
Detailed explanation of parameters
First parameter: filter (required)
- Type:
object
- Description: Update conditions, same as
updateMany
Second parameter: update (required)
- Type:
object
- Description: For update operations, must use update operator (
$set, $inc, $push, etc.)
- ❌ Error:
{ name: 'new' }
- ✅ Correct:
{ $set: { name: 'new' } }
Third parameter: options (optional)
Return value
{
acknowledged: boolean, //Is it confirmed
totalCount: number | null, //Total number of documents (valid when estimateProgress=true)
matchedCount: number, //Number of matching documents
modifiedCount: number, //Number of successful updates
upsertedCount: number, //Always 0 because updateBatch rejects upsert=true
batchCount: number, //Total number of batches
errors: Array<Object>, //error list
retries: Array<Object> //Retry record list
}
Progress callback parameters
{
currentBatch: number, //Current batch number (starting from 1)
totalBatches: number, //Total number of batches
modified: number, //Quantity updated
total: number | null, //Total quantity (valid when estimateProgress=true)
percentage: number | null, //Complete percentage (0-100, has value when estimateProgress=true)
errors: number, //number of errors
retries: number //Number of retries
}
Usage example
1. Basic usage - batch modification status
//Mark all pending orders as processed
const result = await collection('orders').updateBatch(
{ status: 'pending' },
{ $set: { status: 'processed', processedAt: new Date() } },
{ batchSize: 5000 }
);
console.log(`Update ${result.modifiedCount} orders`);
2. With progress monitoring - data migration
const result = await collection('users').updateBatch(
{ oldField: { $exists: true } },
{
$set: { newField: 'value' },
$unset: { oldField: '' }
},
{
batchSize: 5000,
estimateProgress: true,
onProgress: (progress) => {
console.log(`Migration progress: ${progress.percentage}% (${progress.modified}/${progress.total} items)`);
}
}
);
Example output:
Migration progress: 20% (100000/500000 items)
Migration progress: 40% (200000/500000 items)
Migration progress: 60% (300000/500000 items)
Migration progress: 80% (400000/500000 items)
Migration progress: 100% (500000/500000 items)
3. $set - Set field value
//Set user membership levels in batches
await collection('users').updateBatch(
{ registeredDays: { $gte: 365 } },
{
$set: {
vipLevel: 'gold',
vipStartAt: new Date()
}
},
{ batchSize: 5000 }
);
4. $inc - increase or decrease the value
//Increase product inventory in batches
await collection('products').updateBatch(
{ category: 'electronics' },
{
$inc: {
stock: 100, //Inventory +100
version: 1 //version number +1
}
},
{ batchSize: 3000 }
);
5. $push - add array elements
//Add tags to users in batches
await collection('users').updateBatch(
{ isActive: true },
{
$push: {
tags: 'promoted',
notifications: {
type: 'promo',
createdAt: new Date()
}
}
},
{ batchSize: 5000 }
);
6. $pull - delete array elements
//Delete expired notifications in batches
await collection('users').updateBatch(
{ 'notifications.0': { $exists: true } },
{
$pull: {
notifications: {
expiresAt: { $lt: new Date() }
}
}
},
{ batchSize: 5000 }
);
7. $mul - multiplication operation
//Bulk price adjustment (all prices increased by 10%)
await collection('products').updateBatch(
{ price: { $gt: 0 } },
{
$mul: { price: 1.1 },
$set: { updatedAt: new Date() }
},
{
batchSize: 5000,
estimateProgress: true,
onProgress: (p) => console.log(`Price adjustment progress: ${p.percentage}%`)
}
);
8. Multiple operator combinations
//Complex batch update: price adjustment + inventory increase + label addition
await collection('products').updateBatch(
{ category: 'sale' },
{
$mul: { price: 0.8 }, //20% off price
$inc: { stock: 50 }, //Inventory +50
$push: { tags: 'discount' }, //Add discount tag
$set: {
onSale: true,
saleStartAt: new Date()
}
},
{ batchSize: 3000 }
);
9. Using arrayFilters - Update specific elements in an array
//Batch update the status of specific items in an order
await collection('orders').updateBatch(
{ 'items.status': 'pending' },
{
$set: {
'items.$[elem].status': 'shipped',
'items.$[elem].shippedAt': new Date()
}
},
{
batchSize: 3000,
arrayFilters: [{ 'elem.status': 'pending' }]
}
);
10. Error handling - retry strategy (recommended)
const result = await collection('users').updateBatch(
{ lastActive: { $lt: new Date('2024-01-01') } },
{ $set: { status: 'inactive' } },
{
batchSize: 5000,
onError: 'retry',
retryAttempts: 3,
retryDelay: 1000,
onRetry: (info) => {
console.log(`Batch ${info.batchIndex + 1} retry ${info.attempt}`);
}
}
);
console.log(`Update completed, retry ${result.retries.length} times`);
11. Upsert is not supported
updateBatch first selects matching document _id values through a cursor and then updates those _id batches. That batching model has no stable "insert when none matched" target, so upsert: true is rejected.
Use upsertOne() for single-document upserts, or MongoDB-native updateMany(..., { upsert: true }) when you explicitly want server-side multi-update semantics.
MongoDB native note: updateMany(filter, update, { upsert: true }) is supported, but if no documents match, MongoDB inserts one new document derived from the equality parts of filter and the update document. It is not a per-input bulk upsert. For multiple independent keys, run separate upsertOne() calls or use native bulkWrite updateOne models with upsert: true.
//Correct: single-document upsert
await collection('user_settings').upsertOne(
{ userId: 'user_123' },
{
$set: {
theme: 'light',
language: 'en',
updatedAt: new Date()
},
$setOnInsert: {
createdAt: new Date()
}
}
);
12. Complex query conditions
//Batch update documents that meet multiple conditions
await collection('orders').updateBatch(
{
status: 'pending',
createdAt: { $lt: new Date('2024-01-01') },
$or: [
{ paymentStatus: 'paid' },
{ amount: 0 }
]
},
{
$set: {
status: 'cancelled',
cancelledAt: new Date(),
cancelReason: 'Automatically cancel after timeout'
}
},
{
batchSize: 5000,
estimateProgress: true,
onProgress: (p) => console.log(`Cancellation progress: ${p.percentage}%`)
}
);
1. Batch size selection
//Dynamically adjust batch size
const totalCount = await collection('users').count({ status: 'inactive' });
const batchSize = totalCount > 1000000 ? 10000 : 5000;
await collection('users').updateBatch(
{ status: 'inactive' },
{ $set: { status: 'archived' } },
{ batchSize }
);
2. Index optimization
//Make sure there is an index before updating
await collection('orders').createIndex({ status: 1, createdAt: 1 });
//and then update
await collection('orders').updateBatch(
{ status: 'pending', createdAt: { $lt: expireDate } },
{ $set: { status: 'expired' } },
{ batchSize: 5000 }
);
3. Avoid full table scan
//❌ Bad: Unindexed fields
await collection('users').updateBatch(
{ customField: 'value' }, //If customField does not have an index, the entire table will be scanned.
{ $set: { status: 'updated' } }
);
//✅ Good: Use indexed fields
await collection('users').updateBatch(
{ _id: { $in: userIds } }, //_id has a default index
{ $set: { status: 'updated' } }
);
4. Update operation optimization
//❌ Inefficiency: updating the same batch of data multiple times
await collection('users').updateBatch(filter, { $set: { field1: 'a' } });
await collection('users').updateBatch(filter, { $set: { field2: 'b' } });
//✅ Efficient: update multiple fields at once
await collection('users').updateBatch(filter, {
$set: {
field1: 'a',
field2: 'b'
}
});
FAQ
Q1: What is the difference between updateBatch and updateMany?
Suggestions:
- Data volume < 10000 items → use
updateMany
- Data volume ≥ 10000 → use
updateBatch
Q2: Will updateBatch cause data inconsistency?
Answer: updateBatch processes matching _id values through a cursor and writes them in batches. It does not create a MongoDB transaction or guarantee snapshot isolation by itself. If you need a transactional snapshot, run it inside an explicit transaction and pass the transaction session.
//Use a transaction when the batch must share the same transactional snapshot.
await collection('users').updateBatch(
{ status: 'inactive' },
{ $set: { status: 'archived' } },
{ batchSize: 5000 }
);
Q3: Why must we use the update operator?
Answer: This is a MongoDB requirement, as is updateMany.
//❌ Error: direct assignment
await collection('users').updateBatch(
{ status: 'old' },
{ status: 'new' } //will throw an error
);
//✅ Correct: use $set
await collection('users').updateBatch(
{ status: 'old' },
{ $set: { status: 'new' } }
);
Q4: How to update _id in batches?
Answer: Updating _id is not recommended. If necessary, use replaceOne or reinsert.
//❌ Not supported: update _id
await collection('users').updateBatch(
{ oldId: { $exists: true } },
{ $set: { _id: newId } } //will fail
);
//✅ Recommendation: Keep the old ID and add new fields
await collection('users').updateBatch(
{ oldId: { $exists: true } },
{
$set: { newId: generateNewId() },
$unset: { oldId: '' }
}
);
Q5: What update operators does updateBatch support?
A: All MongoDB update operators are supported.
Field operators:
$set - Set field value
$unset - Delete field
$rename - Rename fields
$setOnInsert - Accepted by MongoDB update documents, but only meaningful in upsert operations; updateBatch rejects upsert: true
Numeric operators:
$inc - increase or decrease
$mul - Multiplication
$min - take the minimum value
$max - take the maximum value
Array Operator:
$push - Add element
$pop - delete the first/last elements
$pull - Remove matching elements
$pullAll - delete multiple elements
$addToSet - Add unique element
Other operators:
$currentDate - Set current date
Q6: How to estimate the update time?
//Performance Reference (In-Memory Database)
//Update speed: about 30000-40000 items/second
const totalCount = 1000000;
const estimatedTime = totalCount / 35000; //about 29 seconds
console.log(`Estimated time: ${Math.ceil(estimatedTime)} seconds`);
Q7: Can it be used in transactions?
Answer: Yes.
const session = await msq.startSession();
try {
await session.withTransaction(async () => {
await collection('orders').updateBatch(
{ status: 'pending' },
{ $set: { status: 'processing' } },
{ batchSize: 1000 }
);
await collection('inventory').updateBatch(
{ productId: { $in: productIds } },
{ $inc: { reserved: -1 } },
{ batchSize: 1000 }
);
});
} finally {
await session.endSession();
}
References