Sé libre de compartir nuestra web y códigos en tus redes sociales, danos de tu ayuda para aumentar esta comunidad.
Solución:
No hay una opción de reversión (la reversión tiene un significado diferente en un contexto de MongoDB) y, estrictamente hablando, no hay una forma compatible de recuperar estos documentos; las precauciones que puede / debe tomar se tratan en los comentarios. Dicho esto, sin embargo, si está ejecutando un conjunto de réplicas, incluso un conjunto de réplicas de un solo nodo, entonces tiene un oplog
. Con un oplog
que cubre cuándo se insertaron los documentos, es posible que pueda recuperarlos.
La forma más sencilla de ilustrar esto es con un ejemplo. Usaré un ejemplo simplificado con solo 100 documentos eliminados que deben restaurarse. Para ir más allá (una gran cantidad de documentos, o tal vez solo desee restaurar selectivamente, etc.), querrá cambiar el código para iterar sobre un cursor o escribirlo usando el idioma que elija fuera del shell de MongoDB. La lógica básica sigue siendo la misma.
Primero, creemos nuestra colección de ejemplo foo
en la base de datos dropTest
. Insertaremos 100 documentos sin name
campo y 100 documentos con idéntico name
campo para que se puedan eliminar por error más tarde:
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");
Ahora, simulemos la eliminación accidental de nuestros 100 name
documentos:
> db.foo.remove( "name" : "some_x_name")
WriteResult( "nRemoved" : 100 )
Debido a que estamos ejecutando en un conjunto de réplicas, todavía tenemos un registro de estos documentos en el oplog
(siendo insertado) y afortunadamente esos insertos no se han caído (todavía) del final de la oplog
(el oplog
es una colección limitada, recuerda). Veamos si podemos encontrarlos:
use local;
db.oplog.rs.find(op : "i", ns : "dropTest.foo", "o.name" : "some_x_name").count();
100
El recuento parece correcto, parece que todavía tenemos nuestros documentos. Sé por experiencia que la única parte del oplog
La entrada que necesitaremos aquí es la o
campo, así que agreguemos una proyección para devolver solo eso (salida recortada por brevedad, pero entiendes la idea):
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"
Para volver a insertar esos documentos, podemos simplemente almacenarlos en un array, luego iterar sobre el array e inserte las piezas relevantes. Primero, creemos nuestro array:
var deletedDocs = db.oplog.rs.find(op : "i", ns : "dropTest.foo", "o.name" : "some_x_name", "o" : 1).toArray();
> deletedDocs.length
100
A continuación, recordamos que ahora solo tenemos 100 documentos en la colección, luego recorremos las 100 inserciones y finalmente revalidamos nuestros conteos:
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
Y ahí lo tienes, con algunas salvedades:
- Esto no pretende ser un true estrategia de restauración, mire las copias de seguridad (MMS, otros), secundarios retrasados para eso, como se menciona en los comentarios
- No será particularmente rápido consultar los documentos del registro de operaciones (cualquier consulta de registro de operaciones es un escaneo de tabla) en un sistema grande y ocupado.
- Los documentos pueden envejecer fuera del registro de operaciones en cualquier momento (puede, por supuesto, hacer una copia del registro de operaciones para usarla más adelante y tener más tiempo).
- Dependiendo de su carga de trabajo, es posible que deba eliminar los resultados antes de volver a insertarlos
- Los conjuntos de documentos más grandes serán demasiado grandes para un array como se demostró, por lo que deberá iterar sobre un cursor en su lugar
- El formato del
oplog
se considera interno y puede cambiar en cualquier momento (sin previo aviso), así que úselo bajo su propio riesgo
Si bien entiendo que esto es un poco antiguo, quería compartir algo que investigué en esta área que puede ser útil para otras personas con un problema similar.
El hecho es que MongoDB no elimina físicamente los datos de inmediato, solo los marca para su eliminación. Sin embargo, esto es específico de la versión y actualmente no hay documentación ni estandarización, lo que podría permitir que un desarrollador de herramientas de terceros (o alguien que lo necesite desesperadamente) cree una herramienta o escriba un script simple de manera confiable que funcione en todas las versiones. Abrí un ticket para esto: https://jira.mongodb.org/browse/DOCS-5151.
Exploré una opción que está en un nivel mucho más bajo y puede necesitar un ajuste fino según la versión de MongoDB utilizada. Comprensiblemente, un nivel demasiado bajo para la vinculación de la mayoría de las personas, sin embargo, funciona y puede ser útil cuando todo lo demás falla.
Mi enfoque implica trabajar directamente con el binario en el archivo y usar un script (o comandos) de Python para identificar, leer y descomprimir (BSON) los datos eliminados.
Mi enfoque está inspirado en este proyecto de GitHub (NO soy el desarrollador de este proyecto). Aquí en mi blog he intentado simplificar el script y extraer un registro eliminado específico de un archivo Raw MongoDB.
Actualmente, un registro está marcado para su eliminación como "xee
"al comienzo del registro. Así es como se ve un registro eliminado en el archivo de base de datos sin procesar,
‘xeexeexeexeex07_idx00Ux19xa6gx9fxdfx19xc1xadsxdbxa8x02namex00x04x00x00x00AAAx00x01marksx00x00x00x00x00[email protected][email protected]x00′
Reemplacé el primer bloque con el tamaño del registro que identifiqué anteriormente en base a otros registros.
y=”3x00x00x00″+x[20804:20800+51]
Finalmente, usando el paquete BSON (que viene con pymongo), decodifiqué el binario en un objeto legible.
bson.decode_all(y)
[u’_id': ObjectId(‘5519a6679fdf19c1ad73dba8′), u’name': u’AAA’, u’marks': 2000.0]
Este BSON ahora es un objeto de Python y se puede descargar en una colección de recuperación o simplemente registrarlo en algún lugar.
No hace falta decir que esta o cualquier otra técnica de recuperación debería realizarse idealmente en un área de preparación en una copia de seguridad del archivo de la base de datos.