MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

Quelle est la bonne approche pour mettre à jour de nombreux enregistrements dans MongoDB à l'aide de Mongoose

L'approche consistant à créer un critère composé de tous les identifiants de document, puis à effectuer la mise à jour est susceptible de causer des problèmes potentiels. Lorsque vous parcourez une liste de documents en envoyant une opération de mise à jour avec chaque doc, dans Mongoose, vous courez le risque de faire exploser votre serveur, en particulier lorsque vous traitez un grand ensemble de données, car vous n'attendez pas qu'un appel asynchrone se termine avant de passer au suivant. itération. Vous allez essentiellement construire une "pile" d'opérations non résolues jusqu'à ce que cela pose un problème - Stackoverflow.

Prenons par exemple, supposons que vous disposiez d'un tableau d'identifiants de documents pour lesquels vous vouliez mettre à jour le document correspondant dans le champ d'état :

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

où vous pouvez utiliser le updateMany() méthode

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

ou alternativement pour de très petits ensembles de données, vous pouvez utiliser le forEach() méthode sur le tableau pour l'itérer et mettre à jour votre collection :

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

Ce qui précède convient aux petits ensembles de données. Cependant, cela devient un problème lorsque vous êtes confronté à des milliers ou des millions de documents à mettre à jour, car vous ferez des appels serveur répétés de code asynchrone dans la boucle.

Pour surmonter cela, utilisez quelque chose comme eachLimit et itérer sur le tableau en effectuant une opération de mise à jour MongoDB pour chaque élément sans jamais effectuer plus de x mises à jour parallèles en même temps.

La meilleure approche serait d'utiliser l'API en masse pour cela, qui est extrêmement efficace pour traiter les mises à jour en masse. La différence de performances par rapport à l'appel de l'opération de mise à jour sur chacun des nombreux documents est qu'au lieu d'envoyer les demandes de mise à jour au serveur à chaque itération, l'API en masse envoie les demandes une fois sur 1000 demandes (par lots).

Pour les versions de Mongoose >=4.3.0 qui prennent en charge MongoDB Server 3.2.x , vous pouvez utiliser bulkWrite() pour les mises à jour. L'exemple suivant montre comment procéder :

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

Pour les versions de Mongoose ~3.8.8 , ~3.8.22 , 4.x qui prennent en charge MongoDB Server >=2.6.x , vous pouvez utiliser l'API de transfert en masse comme suit

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