web-dev-qa-db-fra.com

Existe-t-il un moyen de récupérer des documents récemment supprimés dans MongoDB?

J'ai supprimé certains documents dans ma dernière requête par erreur. Existe-t-il un moyen de restaurer ma dernière collection de mongos de requête?.

Voici ma dernière requête:

 db.datas.remove({ "name" : "some_x_name"}) 

Existe-t-il une option de restauration/annulation? Puis-je récupérer mes données?

17
trex

Il n'y a pas d'option de restauration ( la restauration a une signification différente dans un contexte MongoDB), et à proprement parler, il n'y a aucun moyen pris en charge pour récupérer ces documents - les précautions que vous pouvez/devez prendre sont couvertes dans les commentaires . Cela dit, cependant, si vous exécutez un jeu de réplicas, même un jeu de réplicas à nœud unique, vous disposez alors d'un oplog . Avec un oplog qui couvre lorsque les documents ont été insérés, vous pourrez peut-être les récupérer.

La façon la plus simple d'illustrer cela est avec un exemple. J'utiliserai un exemple simplifié avec seulement 100 documents supprimés qui doivent être restaurés. Pour aller au-delà de cela (grand nombre de documents, ou peut-être que vous ne souhaitez restaurer que de manière sélective, etc.), vous souhaiterez soit changer le code pour itérer sur un curseur, soit l'écrire en utilisant la langue de votre choix en dehors du shell MongoDB. La logique de base reste la même.

Commençons par créer notre exemple de collection foo dans la base de données dropTest. Nous allons insérer 100 documents sans champ name et 100 documents avec un champ name identique afin qu'ils puissent être supprimés par erreur ultérieurement:

use dropTest;
for(i=0; i < 100; i++){db.foo.insert({_id : i})};
for(i=100; i < 200; i++){db.foo.insert({_id : i, name : "some_x_name"})};

Simulons maintenant la suppression accidentelle de nos 100 name documents:

> db.foo.remove({ "name" : "some_x_name"})
WriteResult({ "nRemoved" : 100 })

Parce que nous fonctionnons dans un jeu de réplicas, nous avons toujours un enregistrement de ces documents dans le oplog (en cours d'insertion) et heureusement, ces insertions ne sont pas (encore) tombées à la fin du oplog (le oplog est un collection plafonnée souvenez-vous). Voyons voir si nous pouvons les trouver:

use local;
db.oplog.rs.find({op : "i", ns : "dropTest.foo", "o.name" : "some_x_name"}).count();
100

Le décompte semble correct, il semble que nous ayons encore nos documents. Je sais par expérience que la seule partie de l'entrée oplog dont nous aurons besoin ici est le champ o, alors ajoutons une projection pour ne renvoyer que cela (sortie coupée par souci de concision, mais vous obtenez le idée):

db.oplog.rs.find({op : "i", ns : "dropTest.foo", "o.name" : "some_x_name"}, {"o" : 1});
{ "o" : { "_id" : 100, "name" : "some_x_name" } }
{ "o" : { "_id" : 101, "name" : "some_x_name" } }
{ "o" : { "_id" : 102, "name" : "some_x_name" } }
{ "o" : { "_id" : 103, "name" : "some_x_name" } }
{ "o" : { "_id" : 104, "name" : "some_x_name" } }

Pour réinsérer ces documents, nous pouvons simplement les stocker dans un tableau, puis parcourir le tableau et insérer les pièces pertinentes. Commençons par créer notre tableau:

var deletedDocs = db.oplog.rs.find({op : "i", ns : "dropTest.foo", "o.name" : "some_x_name"}, {"o" : 1}).toArray();
> deletedDocs.length
100

Ensuite, nous nous rappelons que nous n'avons maintenant que 100 documents dans la collection, puis parcourons les 100 insertions et revalidons enfin nos décomptes:

use dropTest;
db.foo.count();
100
// simple for loop to re-insert the relevant elements
for (var i = 0; i < deletedDocs.length; i++) {
    db.foo.insert({_id : deletedDocs[i].o._id, name : deletedDocs[i].o.name});
}
// check total and name counts again
db.foo.count();
200
db.foo.count({name : "some_x_name"})
100

Et voilà, avec quelques mises en garde:

  • Ce n'est pas censé être une véritable stratégie de restauration, regardez les sauvegardes (MMS, autres), les secondaires retardés pour cela, comme mentionné dans les commentaires
  • Il ne sera pas particulièrement rapide d'interroger les documents à partir de l'oplog (toute requête oplog est une analyse de table) sur un grand système occupé.
  • Les documents peuvent sortir de l'oplog à tout moment (vous pouvez bien sûr faire une copie de l'oplog pour une utilisation ultérieure afin de vous donner plus de temps)
  • En fonction de votre charge de travail, vous devrez peut-être réduire les résultats avant de les réinsérer
  • Les ensembles de documents plus volumineux seront trop volumineux pour un tableau, comme illustré, vous devrez donc parcourir le curseur à la place
  • Le format du oplog est considéré comme interne et peut changer à tout moment (sans préavis), donc utilisez-le à vos risques et périls
24
Adam Comerford

Bien que je comprenne que c'est un peu vieux mais je voulais partager quelque chose que j'ai recherché dans ce domaine qui pourrait être utile à d'autres personnes ayant un problème similaire.

Le fait est que MongoDB ne supprime pas physiquement les données immédiatement - il les marque uniquement pour suppression. Ceci est cependant spécifique à la version et il n'y a actuellement aucune documentation ou standardisation - ce qui pourrait permettre à un développeur d'outils tiers (ou à quelqu'un dans le besoin désespéré) de construire un outil ou d'écrire un script simple fiable qui fonctionne sur toutes les versions. J'ai ouvert un ticket pour cela - https://jira.mongodb.org/browse/DOCS-5151 .

J'ai exploré une option qui est à un niveau beaucoup plus bas et peut nécessiter un réglage fin en fonction de la version de MongoDB utilisée. Naturellement, le niveau est trop bas pour la liaison de la plupart des gens, mais cela fonctionne et peut être utile lorsque tout le reste échoue.

Mon approche consiste à travailler directement avec le binaire dans le fichier et à utiliser un script (ou des commandes) Python pour identifier, lire et décompresser (BSON) les données supprimées.

Mon approche est inspirée par this projet GitHub (je ne suis PAS le développeur de ce projet). Ici sur mon blog J'ai essayé de simplifier le script et d'extraire un enregistrement supprimé spécifique d'un fichier Raw MongoDB.

Actuellement, un enregistrement est marqué pour suppression comme "\xee" Au début de l'enregistrement. Voici à quoi ressemble un enregistrement supprimé dans le fichier db brut,

‘\xee\xee\xee\xee\x07_id\x00U\x19\xa6g\x9f\xdf\x19\xc1\xads\xdb\xa8\x02name\x00\x04\x00\x00\x00AAA\x00\x01marks\x00\x00\x00\x00\x00\x00@\x9f@\x00′

J'ai remplacé le premier bloc par la taille de l'enregistrement que j'ai identifié plus tôt sur la base d'autres enregistrements.

y=”3\x00\x00\x00″+x[20804:20800+51]

Enfin, en utilisant le paquet BSON (fourni avec pymongo), j'ai décodé le binaire en un objet lisible.

bson.decode_all(y)

[{u’_id': ObjectId(‘5519a6679fdf19c1ad73dba8′), u’name': u’AAA’, u’marks': 2000.0}]

Ce BSON est un objet python maintenant et peut être vidé dans une collection de récupération ou simplement enregistré quelque part.

Inutile de dire que cette technique ou toute autre technique de récupération devrait idéalement être effectuée dans une zone de transfert sur une copie de sauvegarde du fichier de base de données.

10
Yazad