Chaque jour, je reçois un stock de documents (une mise à jour). Ce que je veux faire, c'est insérer chaque élément qui n'existe pas déjà.
J'utilise le pilote Python (pymongo).
Ce que je fais actuellement est (pseudo-code):
for each document in update:
existing_document = collection.find_one(document)
if not existing_document:
document['insertion_date'] = now
else:
document = existing_document
document['last_update_date'] = now
my_collection.save(document)
Mon problème est qu’il est très lent (40 minutes pour moins de 100 000 enregistrements, et j’en ai des millions dans la mise à jour). Je suis à peu près sûr que quelque chose est prévu pour faire cela, mais le document pour update () est mmmhhh .... un peu succinct .... ( http://www.mongodb.org/display/DOCS/Mise à jour )
Quelqu'un peut-il conseiller comment le faire plus rapidement?
On dirait que vous voulez faire un "upsert". MongoDB a un support intégré pour cela. Passez un paramètre supplémentaire à votre appel à update (): {upsert: true}. Par exemple:
key = {'key':'value'}
data = {'key2':'value2', 'key3':'value3'};
coll.update(key, data, upsert=True); #In python upsert must be passed as a keyword argument
Ceci remplace entièrement votre bloc if-find-else-update. Il insérera si la clé n'existe pas et se mettra à jour si c'est le cas.
Avant:
{"key":"value", "key2":"Ohai."}
Après:
{"key":"value", "key2":"value2", "key3":"value3"}
Vous pouvez également spécifier les données que vous souhaitez écrire:
data = {"$set":{"key2":"value2"}}
Maintenant, votre document sélectionné mettra à jour la valeur de "key2" uniquement et laissera tout le reste intact.
A partir de MongoDB 2.4, vous pouvez utiliser $ setOnInsert ( http://docs.mongodb.org/manual/reference/operator/setOnInsert/ )
Définissez 'insertion_date' en utilisant $ setOnInsert et 'last_update_date' en utilisant $ set dans votre commande upsert.
Pour transformer votre pseudocode en exemple de travail:
now = datetime.utcnow()
for document in update:
collection.update_one(
{"_id": document["_id"]},
{
"$setOnInsert": {"insertion_date": now},
"$set": {"last_update_date": now},
},
upsert=True,
)
Vous pouvez toujours créer un index unique, ce qui oblige MongoDB à rejeter une sauvegarde en conflit. Considérez ce qui suit avec le shell mongodb:
> db.getCollection("test").insert ({a:1, b:2, c:3})
> db.getCollection("test").find()
{ "_id" : ObjectId("50c8e35adde18a44f284e7ac"), "a" : 1, "b" : 2, "c" : 3 }
> db.getCollection("test").ensureIndex ({"a" : 1}, {unique: true})
> db.getCollection("test").insert({a:2, b:12, c:13}) # This works
> db.getCollection("test").insert({a:1, b:12, c:13}) # This fails
E11000 duplicate key error index: foo.test.$a_1 dup key: { : 1.0 }
Vous pouvez utiliser Upsert avec l'opérateur $ setOnInsert.
db.Table.update({noExist: true}, {"$setOnInsert": {xxxYourDocumentxxx}}, {upsert: true})
S'inspirant de la réponse de Van Nguyen ci-dessus, utilisez update au lieu de save. Cela vous donne accès à l'option upsert.
[~ # ~] note [~ # ~] : cette méthode remplace le document entier lorsqu'elle est trouvée ( parmi les documents ).
var conditions = { name: 'borne' } , update = { $inc: { visits: 1 }} , options = { multi: true };
Model.update(conditions, update, options, callback);
function callback (err, numAffected) { // numAffected is the number of updated documents })
Si vous souhaitez mettre à jour une sélection du document, mais pas le tout, vous pouvez utiliser la méthode $ set avec update. (encore une fois, de la documentation ) ... Donc, si vous voulez définir ...
var query = { name: 'borne' }; Model.update(query, ***{ name: 'jason borne' }***, options, callback)
Envoyez le comme ...
Model.update(query, ***{ $set: { name: 'jason borne' }}***, options, callback)
Cela évite d’écraser accidentellement l’ensemble de vos documents avec { name: 'jason borne' }
.
Je ne pense pas que mongodb soutienne ce type de upserting sélectif. J'ai le même problème que LeMiz, et utiliser pdate (critères, newObj, upsert, multi) ne fonctionne pas correctement avec un horodatage 'créé' et 'mis à jour'. Compte tenu de la déclaration suivante UPS:
update( { "name": "abc" },
{ $set: { "created": "2010-07-14 11:11:11",
"updated": "2010-07-14 11:11:11" }},
true, true )
Scénario n ° 1 - le document avec 'nom' de 'abc' n'existe pas: un nouveau document est créé avec 'nom' = 'abc', 'created' = 2010-07-14 11:11:11 et 'updated' = 2010-07-14 11:11:11.
Scénario n ° 2 - le document avec 'nom' de 'abc' existe déjà avec les éléments suivants: 'name' = 'abc', 'created' = 2010-07-12 09:09:09 et 'updated' = 2010-07 -13 10:10:10. Après la conversion, le document serait maintenant identique au résultat du scénario n ° 1. Il n'y a aucun moyen de spécifier dans un upsert quels champs doivent être définis en cas d'insertion et quels champs doivent être laissés seuls lors d'une mise à jour.
Ma solution consistait à créer un index unique sur les champs critère, à effectuer une insertion et à effectuer ensuite une mise à jour uniquement sur le champ "mis à jour".
Résumé
Notez que je présume que PyMongo, changez-le en fonction de la langue de votre choix.
Instructions:
Créez la collection avec un index unique = true afin d'éviter les doublons.
Parcourez vos enregistrements d’entrée en créant des lots de 15 000 enregistrements environ. Pour chaque enregistrement du lot, créez un dict contenant les données que vous souhaitez insérer, en supposant que chaque enregistrement sera un nouvel enregistrement. Ajoutez les horodatages "créé" et "mis à jour" à ceux-ci. Émettez ceci en tant que commande d'insertion par lot avec l'indicateur 'ContinueOnError' = = true, de sorte que l'insertion de tout le reste se produise même s'il y a une clé dupliquée (ce qui semble être le cas). CELA ARRIVERA TRES RAPIDEMENT. Bulk inserts rock, j'ai atteint les 15k/seconde de performance. Des notes complémentaires sur ContinueOnError, voir http://docs.mongodb.org/manual/core/write-operations/
Les insertions de disques arrivent TRES rapidement, vous aurez donc fini avec ces insertions en un rien de temps. Maintenant, il est temps de mettre à jour les enregistrements pertinents. Faites cela avec une récupération par lot, beaucoup plus rapide qu'un à la fois.
Parcourez à nouveau tous vos enregistrements d’entrée en créant des lots de 15 Ko environ. Extrayez les clés (mieux s’il ya une clé, mais ne peut pas vous aider si ce n’est pas le cas). Récupérez ce lot d'enregistrements de Mongo avec une requête db.collectionNameBlah.find ({field: {$ in: [1, 2,3 ...}). Pour chacun de ces enregistrements, déterminez s'il existe une mise à jour et, le cas échéant, lancez la mise à jour, y compris en mettant à jour l'horodatage "mis à jour".
Malheureusement, il convient de noter que MongoDB 2.4 et versions antérieures n'incluent pas une opération de mise à jour en masse. Ils y travaillent.
Points d'optimisation clés:
En général, utiliser update est préférable dans MongoDB, car il ne créera le document que s’il n’existe pas encore, bien que je ne sois pas sûr de l’utiliser avec votre adaptateur python.
Deuxièmement, si vous avez seulement besoin de savoir si ce document existe ou non, count (), qui renvoie uniquement un nombre, sera une meilleure option que find_one, qui est supposé transférer l'intégralité du document de votre MongoDB, générant ainsi un trafic inutile.