updateBatch - Update documents in batches
API parameter description
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