MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

Was ist der richtige Ansatz, um viele Datensätze in MongoDB mit Mongoose zu aktualisieren

Der Ansatz, ein Kriterium aufzubauen, das aus allen Dokument-IDs besteht, und dann die Aktualisierung durchzuführen, führt zwangsläufig zu potenziellen Problemen. Wenn Sie eine Liste von Dokumenten iterieren und mit jedem Dokument einen Aktualisierungsvorgang senden, laufen Sie in Mongoose Gefahr, Ihren Server zu sprengen, insbesondere wenn Sie mit einem großen Datensatz arbeiten, da Sie nicht auf den Abschluss eines asynchronen Aufrufs warten, bevor Sie mit dem nächsten fortfahren Wiederholung. Sie werden im Wesentlichen einen "Stapel" von ungelösten Vorgängen aufbauen, bis dies ein Problem verursacht - Stackoverflow.

Nehmen wir zum Beispiel an, Sie hätten ein Array von Dokument-IDs, mit denen Sie das übereinstimmende Dokument im Statusfeld aktualisieren möchten:

const processedIds = [
  "57a0a96bd1c6ef24376477cd",
  "57a052242acf5a06d4996537",
  "57a052242acf5a06d4996538"
];

wo Sie updateMany() verwenden können Methode

Model.updateMany(
  { _id: { $in: processedIds } }, 
  { $set: { status: "processed" } }, 
  callback
);

Alternativ können Sie für wirklich kleine Datensätze den forEach() -Methode auf dem Array, um es zu iterieren und Ihre Sammlung zu aktualisieren:

processedIds.forEach(function(id)){
  Model.update({ _id: id}, { $set: { status: "processed" } }, callback);
});

Das Obige ist für kleine Datensätze in Ordnung. Dies wird jedoch zu einem Problem, wenn Sie mit Tausenden oder Millionen von zu aktualisierenden Dokumenten konfrontiert sind, da Sie innerhalb der Schleife wiederholt Serveraufrufe von asynchronem Code durchführen.

Um dies zu umgehen, verwenden Sie so etwas wie eachLimit und über das Array iterieren, indem für jedes Element eine MongoDB-Aktualisierungsoperation durchgeführt wird, wobei niemals mehr als x parallele Aktualisierungen gleichzeitig durchgeführt werden.

Der beste Ansatz wäre, hierfür die Bulk-API zu verwenden, die bei der Verarbeitung von Updates in großen Mengen äußerst effizient ist. Der Leistungsunterschied zum Aufrufen des Aktualisierungsvorgangs für jedes einzelne der vielen Dokumente besteht darin, dass die Massen-API die Anforderungen einmal alle 1000 Anforderungen (gestapelt) sendet, anstatt die Aktualisierungsanforderungen bei jeder Iteration an den Server zu senden.

Für Mongoose-Versionen >=4.3.0 die MongoDB Server 3.2.x unterstützen , können Sie bulkWrite() für Aktualisierungen. Das folgende Beispiel zeigt, wie Sie vorgehen können:

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);
}

Für Mongoose-Versionen ~3.8.8 , ~3.8.22 , 4.x die MongoDB Server >=2.6.x unterstützen , könnten Sie die Bulk-API wie folgt verwenden

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
    });
}