2016-02-05 1 views
2

J'ai 2 collections:globale et mise à jour MongoDB

  • clients (6 000 000 documents)
  • commandes (50 000 000 documents)

Une fois par jour, je voudrais calculer le nombre de commandes de l'année écoulée, du mois passé et de la semaine écoulée, et ainsi de suite, par client.

J'ai essayé ceci:

db.orders.aggregate(
    {$match: 
     { date_order: { $gt: v_date1year } } 
    }, 
    {$group : { 
     _id : "$id_client", 
     count : {$sum : 1} 
    }} , 
    { 
     "$out": "tmp_indicators" 
    } 
) 

db.tmp_indicators.find({}).forEach(function (my_client) { 
    db.clients.update (
     {"id_client": my_client._id}, 
     {"$set": 
      { "nb_orders_1year" : my_client.count } 
     } 
    ) 
}) 

Je dois faire cela 3 fois, 1 pour l'agrégation de la dernière année, 1 pour le mois passé et 1 pour la semaine dernière. Le traitement est très lent, avez-vous une idée de comment l'améliorer?

Répondre

6

Pour améliorer les performances en particulier lorsqu'ils traitent avec de grandes collections, tirer profit de l'utilisation du Bulk() API pour les mises à jour en vrac que vous enverrez les opérations sur le serveur par lots (par exemple, par exemple une taille de lot de 1000) qui vous donne de meilleures performances car vous n'enverrez pas toutes les requêtes au serveur (comme vous le faites actuellement avec l'instruction update dans la boucle forEach()) mais seulement une fois sur 1000 demandes, rendant ainsi vos mises à jour plus efficaces et plus rapides qu'actuellement.

Les exemples suivants illustrent cette approche, le premier utilise l'API Bulk() disponible dans les versions MongoDB >= 2.6 and < 3.2. Il met à jour tous les documents de la collection clients en modifiant les champs nb_orders_1year avec des valeurs provenant des résultats d'agrégation.

Puisque la méthode aggregate() retourne une cursor, Vous pouvez utiliser la méthode de collecte de sortie d'agrégation forEach() pour itérer et accéder à chaque document établissant ainsi les opérations de mise à jour en vrac par lots pour envoyer ensuite à travers le serveur efficacement avec l'API:

var bulk = db.clients.initializeUnorderedBulkOp() 
    pipeline = [ 
     { 
      "$match": { "date_order": { "$gt": v_date1year } } 
     }, 
     { 
      "$group": { 
       "_id": "$id_client", 
       "count": { "$sum" : 1 } 
      } 
     }, 
     { "$out": "tmp_indicators" }   
    ], 
    counter = 0; 
db.orders.aggregate(pipeline); 
db.tmp_indicators.find().forEach(function (doc) {  
    bulk.find({ "_id": doc._id }).updateOne({ 
     "$set": { "nb_orders_1year": doc.count } 
    }); 

    counter++; 
    if (counter % 1000 == 0) { 
     bulk.execute(); // Execute per 1000 operations and re-initialize every 1000 update statements 
     bulk = db.clients.initializeUnorderedBulkOp(); 
    } 
}); 
// Clean up remaining operations in queue 
if (counter % 1000 != 0) { bulk.execute(); } 

l'exemple suivant applique à la nouvelle version MongoDB 3.2 qui a depuis deprecated the Bulk API et a fourni un ensemble de nouvelles API en utilisant bulkWrite().

Il utilise le même curseur comme ci-dessus, mais au lieu de itérer le résultat, créez le tableau avec les opérations en vrac en utilisant sa méthode map():

var pipeline = [ 
     { 
      "$match": { "date_order": { "$gt": v_date1year } } 
     }, 
     { 
      "$group": { 
       "_id": "$id_client", 
       "count": { "$sum" : 1 } 
      } 
     }, 
     { "$out": "tmp_indicators" }   
    ]; 
db.orders.aggregate(pipeline); 
var bulkOps = db.tmp_indicators.find().map(function (doc) { 
     return { 
      "updateOne": { 
       "filter": { "_id": doc._id } ,    
       "update": { "$set": { "nb_orders_1year": doc.count } } 
      }   
     }; 
    }); 

db.clients.bulkWrite(bulkOps, { "ordered": true }); 
+0

Merci pour la solution, mais en utilisant un curseur ne travailler en raison de la limitation de taille du document. J'ai ce problème "exception: résultat d'agrégation dépasse la taille maximale du document (16 Mo)". C'est pourquoi j'ai utilisé l'utilitaire "$ out" – Mouette

+1

@Mouette Vous avez raison, je n'avais pas factorisé cela dans ma réponse initiale mais j'ai depuis mis à jour ma réponse pour utiliser le curseur de la collection de sortie d'agrégation. – chridam

+0

Si la première solution, quel est le but de l'exécution du volume toutes les 1000 opérations? L'utilitaire en vrac ne peut-il pas gérer 6 000 000 mises à jour (1 par client max) en une seule fois? – Mouette