Есть ли способ восстановить недавно удаленные документы в MongoDB?

Я удалил некоторые документы в моем последнем запросе по ошибке, есть ли способ отбросить мою последнюю коллекцию запросов mongo.

Вот мой последний запрос:

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

Есть ли опция отката/отмены? Могу ли я вернуть данные?

Ответы

Ответ 1

Нет опции отката (откат имеет другое значение в контексте MongoDB), и, строго говоря, нет способа поддержать эти документы - меры предосторожности вы можете/должны принять, рассматриваются в комментариях. С учетом сказанного, однако, если вы используете набор реплик, даже один набор реплик node, то у вас есть oplog. С oplog, который будет закрыт при вставке документов, вы сможете восстановить их.

Самый простой способ проиллюстрировать это - пример. Я использую упрощенный пример с только 100 удаленными документами, которые необходимо восстановить. Чтобы выйти за рамки этого (огромное количество документов или, возможно, вы хотите только выборочно восстановить и т.д.), Вы либо захотите изменить код, чтобы перебрать курсор, либо написать его, используя свой язык выбора вне оболочки MongoDB. Основная логика остается прежней.

Сначала создайте наш пример коллекции foo в базе данных dropTest. Мы вставим 100 документов без поля name и 100 документов с идентичным полем name, чтобы их можно было по ошибке удалить позже:

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

Теперь давайте смоделируем случайное удаление наших 100 name документов:

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

Поскольку мы работаем в наборе реплик, у нас все еще есть запись этих документов в oplog (вставляется), и, к счастью, эти вставки еще не упали с конца oplog (oplogявляется закрытой коллекцией). Посмотрим, найдем ли мы их:

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

Счет выглядит правильно, у нас, похоже, все еще есть наши документы. Я знаю по опыту, что единственный фрагмент записи oplog, который нам понадобится здесь, - это поле o, поэтому добавьте проекцию только для возврата (вывод сокращен для краткости, но вы получите идею):

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" } }

Чтобы повторно вставить эти документы, мы можем просто сохранить их в массиве, затем перебрать массив и вставить соответствующие фрагменты. Сначала создайте наш массив:

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

Затем мы напоминаем себе, что теперь у нас есть только 100 документов в коллекции, затем перебираем 100 вставок и, наконец, подтверждаем наши подсчеты:

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

И у вас есть это, с некоторыми оговорками:

  • Это не значит, что это настоящая стратегия восстановления, посмотрите на резервные копии (MMS, другие), отсроченные второстепенные для этого, как указано в комментариях.
  • Не будет особенно быстро запрашивать документы из oplog (любой запрос oplog - это сканирование таблицы) в большой загруженной системе.
  • В любой момент документы могут возрасти из oplog (вы можете, конечно, сделать копию oplog для последующего использования, чтобы дать вам больше времени).
  • В зависимости от вашей рабочей нагрузки вам, возможно, придется обнулить результаты перед их повторной установкой.
  • Большие наборы документов будут слишком большими для массива, как показано, поэтому вам нужно будет перебирать курсор вместо
  • Формат oplog считается внутренним и может меняться в любое время (без уведомления), поэтому используйте на свой страх и риск

Ответ 2

Хотя я понимаю, что это немного устарело, но я хотел поделиться тем, что я исследовал в этой области, что может быть полезно для других с аналогичной проблемой.

Дело в том, что MongoDB не физически удаляет данные немедленно - он только маркирует его для удаления. Однако это зависит от версии, и в настоящее время нет документации или стандартизации, что может помочь стороннему разработчику инструмента (или кому-то в отчаянной потребности) построить инструмент или надежно написать простой script, который работает в разных версиях. Я открыл билет для этого - https://jira.mongodb.org/browse/DOCS-5151.

Я изучил один вариант, который находится на гораздо более низком уровне и может нуждаться в тонкой настройке на основе используемой версии MongoDB. Понятно, что слишком низкий уровень для большинства людей, связанных с ним, однако он работает и может быть полезен, когда все остальное не работает.

Мой подход предполагает непосредственную работу с двоичным файлом в файле и использование Python script (или команд) для идентификации, чтения и распаковки (BSON) удаленных данных.

Мой подход вдохновлен этим проектом GitHub (я НЕ являюсь разработчиком этого проекта). Здесь, в моем блоге, я попытался упростить script и извлечь определенную удаленную запись из файла Raw MongoDB.

В настоящее время запись помечена для удаления как "\xee" в начале записи. Вот как выглядит удаленная запись в необработанном db файле,

‘\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\[email protected]\[email protected]\x00′

Я заменил первый блок размером записи, которую я определил ранее, на основе других записей.

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

Наконец, используя BSON-пакет (который поставляется с pymongo), я декодировал двоичный файл для Readable.

bson.decode_all(y)

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

Этот BSON теперь является объектом python и может быть сброшен в коллекцию восстановления или просто зарегистрирован где-то.

Излишне говорить, что эта или любая другая техника восстановления должна быть идеально выполнена в промежуточной области на резервной копии файла базы данных.