Podejście polegające na zbudowaniu kryterium składającego się ze wszystkich identyfikatorów dokumentów, a następnie wykonaniu aktualizacji, może powodować potencjalne problemy. Kiedy iterujesz listę dokumentów wysyłając operację aktualizacji z każdym dokumentem, w Mongoose ryzykujesz wysadzenie serwera, zwłaszcza gdy masz do czynienia z dużym zbiorem danych, ponieważ nie czekasz na zakończenie asynchronicznego wywołania przed przejściem do następnego iteracja. Zasadniczo będziesz budować „stos” nierozwiązanych operacji, dopóki nie spowoduje to problemu - Stackoverflow.
Weźmy na przykład, załóżmy, że masz tablicę identyfikatorów dokumentów, które chcesz zaktualizować pasujący dokument w polu stanu:
const processedIds = [
"57a0a96bd1c6ef24376477cd",
"57a052242acf5a06d4996537",
"57a052242acf5a06d4996538"
];
gdzie możesz użyć updateMany()
metoda
Model.updateMany(
{ _id: { $in: processedIds } },
{ $set: { status: "processed" } },
callback
);
lub alternatywnie dla naprawdę małych zestawów danych możesz użyć forEach()
metoda na tablicy, aby ją iterować i aktualizować swoją kolekcję:
processedIds.forEach(function(id)){
Model.update({ _id: id}, { $set: { status: "processed" } }, callback);
});
Powyższe jest w porządku dla małych zestawów danych. Jednak staje się to problemem, gdy masz do czynienia z tysiącami lub milionami dokumentów do zaktualizowania, ponieważ będziesz wykonywać powtarzające się wywołania serwera kodu asynchronicznego w pętli.
Aby rozwiązać ten problem, użyj czegoś takiego jak eachLimit
i iteruj po tablicy, wykonując operację aktualizacji MongoDB dla każdego elementu, nigdy nie wykonując jednocześnie więcej niż x równoległych aktualizacji.
Najlepszym podejściem byłoby użycie do tego celu zbiorczego interfejsu API, który jest niezwykle wydajny w przetwarzaniu zbiorczych aktualizacji. Różnica w wydajności w porównaniu z wywoływaniem operacji aktualizacji na każdym z wielu dokumentów polega na tym, że zamiast wysyłać żądania aktualizacji do serwera w każdej iteracji, zbiorczy interfejs API wysyła żądania raz na 1000 żądań (wsadowo).
Dla wersji Mongoose >=4.3.0
które obsługują serwer MongoDB 3.2.x
, możesz użyć bulkWrite()
o aktualizacje. Poniższy przykład pokazuje, jak możesz się do tego zabrać:
const bulkUpdateCallback = function(err, r){
console.log(r.matchedCount);
console.log(r.modifiedCount);
}
// Initialize the bulk operations array
const 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 Node.js driver collection object
Model.collection.bulkWrite(bulkUpdateOps, { ordered: true, w: 1 }, bulkUpdateCallback);
bulkUpdateOps = []; // re-initialize
}
})
// Flush any remaining bulk ops
if (counter % 500 != 0) {
Model.collection.bulkWrite(bulkOps, { ordered: true, w: 1 }, bulkUpdateCallback);
}
Dla wersji Mongoose ~3.8.8
, ~3.8.22
, 4.x
które obsługują serwer MongoDB >=2.6.x
, możesz użyć Bulk API w następujący sposób
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
});
}