web-dev-qa-db-fra.com

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

Je tire certains enregistrements de MongoDB à l'aide de Mongoose, les importe dans un autre système, puis je voudrais définir le statut (attribut de document) pour tous ces documents sur processed.

J'ai pu trouver cette solution: Mettre à jour plusieurs documents par ensemble d'ID. Mongoose

Je me demandais si c'était la bonne approche, pour construire un critère composé de tous les identifiants de document, puis effectuer la mise à jour. Veuillez également prendre en compte le fait qu'il va y avoir de nombreux documents.

(Quelle est la limite de la requête de mise à jour? Impossible de la trouver n'importe où. Documentation officielle: http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html )

17
Ondrej Tokar

L'approche consistant à créer un critère composé de tous les identifiants de document, puis à effectuer la mise à jour est susceptible de provoquer des problèmes potentiels. Lorsque vous parcourez une liste de documents envoyant une opération de mise à jour avec chaque document, 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 la fin d'un appel asynchrone avant de passer au suivant itération. Vous allez essentiellement construire une "pile" d'opérations non résolues jusqu'à ce que cela cause un problème - Stackoverflow.

Prenons par exemple, en supposant que vous disposiez d'un tableau d'ID de document que vous vouliez mettre à jour le document correspondant dans le champ d'état:

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

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

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

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

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

Ce qui précède est correct pour les 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 effectuerez des appels répétés de code asynchrone sur le serveur dans la boucle.

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


La meilleure approche serait d'utiliser l'API en bloc pour cela, ce qui est extrêmement efficace dans le traitement des mises à jour en bloc. 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 bloc envoie les demandes une fois toutes les 1000 demandes (par lots).

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

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

Pour les versions Mongoose ~3.8.8, ~3.8.22, 4.x Qui prennent en charge le serveur MongoDB >=2.6.x, Vous pouvez utiliser l'API Bulk 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
    });
}
22
chridam

Vous pouvez utiliser {multi: true} dans votre requête de mise à jour pour une mise à jour groupée.

Exemple:

employees.update({ _id: { $gt: 3 } },{$inc: { sortOrder: -1 }},{'multi':true});

Le code ci-dessus en mangouste est équivalent au code ci-dessous en mongodb:

db.employees.updateMany({ _id: { $gt: 3 } },{$inc: { sortOrder: -1 }});
6
Avinash