MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Qual è l'approccio giusto per aggiornare molti record in MongoDB usando Mongoose

L'approccio di creare un criterio composto da tutti gli ID documento e quindi eseguire l'aggiornamento è destinato a causare potenziali problemi. Quando ripeti un elenco di documenti inviando un'operazione di aggiornamento con ogni documento, in Mongoose corri il rischio di far saltare in aria il tuo server soprattutto quando hai a che fare con un set di dati di grandi dimensioni perché non stai aspettando il completamento di una chiamata asincrona prima di passare al successivo iterazione. In sostanza, creerai una "pila" di operazioni irrisolte fino a quando ciò non causerà un problema:Stackoverflow.

Prendi ad esempio, supponendo di avere un array di ID documento che desideri aggiornare il documento corrispondente nel campo dello stato:

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

dove puoi utilizzare updateMany() metodo

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

o in alternativa per set di dati davvero piccoli è possibile utilizzare forEach() metodo sull'array per iterarlo e aggiornare la tua raccolta:

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

Quanto sopra va bene per piccoli set di dati. Tuttavia, questo diventa un problema quando ci si trova di fronte a migliaia o milioni di documenti da aggiornare poiché si effettuano ripetute chiamate al server di codice asincrono all'interno del ciclo.

Per ovviare a questo, usa qualcosa come eachLimit e scorrere l'array eseguendo un'operazione di aggiornamento MongoDB per ogni elemento senza mai eseguire più di x aggiornamenti paralleli contemporaneamente.

L'approccio migliore sarebbe utilizzare l'API in blocco per questo che è estremamente efficiente nell'elaborazione degli aggiornamenti in blocco. La differenza in termini di prestazioni rispetto alla chiamata dell'operazione di aggiornamento su ciascuno dei molti documenti è che invece di inviare le richieste di aggiornamento al server con ogni iterazione, l'API di massa invia le richieste una volta ogni 1000 richieste (in batch).

Per le versioni di Mongoose >=4.3.0 che supportano MongoDB Server 3.2.x , puoi utilizzare bulkWrite() per gli aggiornamenti. L'esempio seguente mostra come procedere:

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

Per le versioni di Mongoose ~3.8.8 , ~3.8.22 , 4.x che supportano MongoDB Server >=2.6.x , puoi utilizzare l'API in blocco come segue

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