Ответ 1
Подход к построению критерия, состоящего из всех идентификаторов документов и последующему обновлению, неизбежно вызовет потенциальные проблемы. Когда вы выполняете итерацию списка документов, отправляющих операцию обновления с каждым документом, в Mongoose вы рискуете взорвать свой сервер, особенно при работе с большим набором данных, поскольку вы не ожидаете завершения асинхронного вызова, прежде чем переходить к следующему итерация. По сути, вы будете создавать "стек" неразрешенных операций, пока это не вызовет проблему - Stackoverflow.
Возьмем, к примеру, предположим, что у вас есть массив идентификаторов документов, которые вы хотите обновить соответствующий документ в поле состояния:
var processedIds = [
"57a0a96bd1c6ef24376477cd",
"57a052242acf5a06d4996537",
"57a052242acf5a06d4996538"
];
где вы можете использовать метод updateMany()
method
Model.updateMany(
{ "_id": { "$in": processedIds } },
{ "$set": { "status": "processed" } },
callback
);
или, наоборот, для действительно небольших наборов данных вы можете использовать метод forEach()
в массиве, чтобы выполнить итерацию и обновить свою коллекцию:
processedIds.forEach(function(id)){
Model.update({"_id": id}, {"$set": {"status": "processed" }}, callback);
});
Вышесказанное подходит для небольших наборов данных. Однако это становится проблемой, когда вы сталкиваетесь с тысячами или миллионами документов для обновления, так как вы будете делать повторные вызовы сервера асинхронного кода в цикле.
Чтобы преодолеть это, используйте что-то вроде async eachLimit
и итерируйте по массиву, выполняя операцию обновления MongoDB для каждого элемента, не выполняя одновременно более x параллельных обновлений.
Лучшим подходом было бы использовать для этого пакетный API, который чрезвычайно эффективен при массовой обработке обновлений. Разница в производительности по сравнению с вызовом операции обновления для каждого из множества документов заключается в том, что вместо отправки запросов на обновление на сервер с каждой итерацией массовый API отправляет запросы один раз в каждую 1000 запросов (в пакетном режиме).
Для версий Mongoose >=4.3.0
, которые поддерживают MongoDB Server 3.2.x
, вы можете использовать bulkWrite()
для обновлений. В следующем примере показано, как вы можете это сделать:
var bulkUpdateCallback = function(err, r){
console.log(r.matchedCount);
console.log(r.modifiedCount);
}
// Initialise the bulk operations array
var bulkUpdateOps = [],
counter = 0;
processedIds.forEach(function(id) {
bulkUpdateOps.push({
"updateOne": {
"filter": { "_id": id },
"update": { "$set": { "status": "processed" } }
}
});
counter++;
if (counter % 500 == 0) {
// Get the underlying collection via the native node.js driver collection object
Model.collection.bulkWrite(bulkOps, { "ordered": true, w: 1 }, bulkUpdateCallback);
bulkUpdateOps = []; // re-initialize
}
})
if (counter % 500 != 0) { Model.collection.bulkWrite(bulkOps, { "ordered": true, w: 1 }, bulkUpdateCallback); }
Для версий Mongoose ~3.8.8
, ~3.8.22
, 4.x
, которые поддерживают Сервер MongoDB >=2.6.x
, вы можете использовать Bulk API следующим образом
var bulk = Model.collection.initializeOrderedBulkOp(),
counter = 0;
processedIds.forEach(function(id) {
bulk.find({ "_id": id }).updateOne({
"$set": { "status": "processed" }
});
counter++;
if (counter % 500 == 0) {
bulk.execute(function(err, r) {
// do something with the result
bulk = Model.collection.initializeOrderedBulkOp();
counter = 0;
});
}
});
// Catch any docs in the queue under or over the 500's
if (counter > 0) {
bulk.execute(function(err,result) {
// do something with the result here
});
}