Способы реализации управления версиями данных в MongoDB

Можете ли вы поделиться своими мыслями о том, как реализовать внедрение версий данных в MongoDB. (Я спросил аналогичный вопрос о Кассандре. Если у вас есть мысли, которые лучше для этого, пожалуйста, поделитесь)

Предположим, что мне нужно записать записи в простую адресную книгу. (Записи адресной книги хранятся как плоские объекты json). Я ожидаю, что история:

  • будет использоваться нечасто
  • будет использоваться все сразу, чтобы представить его в режиме "машины времени".
  • не будет больше версий, чем несколько сотен в одной записи. история не истечет.

Я рассматриваю следующие подходы:

  • Создайте новую коллекцию объектов для хранения истории записей или изменений в записях. Он сохранит один объект на версию со ссылкой на запись в адресной книге. Такие записи выглядят следующим образом:

    {
     '_id': 'new id',
     'user': user_id,
     'timestamp': timestamp,
     'address_book_id': 'id of the address book record' 
     'old_record': {'first_name': 'Jon', 'last_name':'Doe' ...}
    }
    

    Этот подход может быть изменен для хранения массива версий на один документ. Но это, кажется, более медленный подход без каких-либо преимуществ.

  • Сохранять версии как сериализованные (JSON) объекты, прикрепленные к записям адресной книги. Я не уверен, как присоединить такие объекты к документам MongoDB. Возможно, как массив строк. (Моделируется после простого документооборота с помощью CouchDB)

Ответ 1

Первый большой вопрос, когда нужно погрузиться в это, - "как вы хотите хранить изменения"?

  • Diffs?
  • Всего записей?

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

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

Чтобы сделать мою жизнь легкой, я бы сделал документ истории, содержащий словарь с метками времени. Что-то вроде этого:

{
    _id : "id of address book record",
    changes : { 
                1234567 : { "city" : "Omaha", "state" : "Nebraska" },
                1234568 : { "city" : "Kansas City", "state" : "Missouri" }
               }
}

Чтобы сделать мою жизнь очень простой, я бы сделал эту часть своих DataObjects (EntityWrapper, что угодно), которые я использую для доступа к моим данным. Обычно эти объекты имеют некоторую форму истории, поэтому вы можете легко переопределить метод save(), чтобы сделать это изменение в одно и то же время.

ОБНОВЛЕНИЕ: 2015-10

Похоже, теперь есть спецификация для обработки JSON diffs. Это похоже на более надежный способ хранения различий/изменений.

Ответ 2

Существует схема управления версиями, называемая "Вермонго", которая затрагивает некоторые аспекты, которые не были рассмотрены в других ответах.

Одной из этих проблем является одновременное обновление, другое - удаление документов.

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

https://github.com/thiloplanz/v7files/wiki/Vermongo

Ответ 4

Здесь другое решение, использующее один документ для текущей версии и всех старых версий:

{
    _id: ObjectId("..."),
    data: [
        { vid: 1, content: "foo" },
        { vid: 2, content: "bar" }
    ]
}

data содержит все версии. Массив data упорядочен, новые версии получат только $push ed до конца массива. data.vid - это идентификатор версии, который является увеличивающимся числом.

Получить самую последнюю версию:

find(
    { "_id":ObjectId("...") },
    { "data":{ $slice:-1 } }
)

Получить определенную версию vid:

find(
    { "_id":ObjectId("...") },
    { "data":{ $elemMatch:{ "vid":1 } } }
)

Возвращает только указанные поля:

find(
    { "_id":ObjectId("...") },
    { "data":{ $elemMatch:{ "vid":1 } }, "data.content":1 }
)

Вставить новую версию: (и предотвратить одновременную вставку/обновление)

update(
    {
        "_id":ObjectId("..."),
        $and:[
            { "data.vid":{ $not:{ $gt:2 } } },
            { "data.vid":2 }
        ]
    },
    { $push:{ "data":{ "vid":3, "content":"baz" } } }
)

2 - это vid текущей последней версии, а 3 - вставленная новая версия. Поскольку вам нужна самая последняя версия vid, легко получить следующую версию vid: nextVID = oldVID + 1.

Условие $and гарантирует, что 2 является последним vid.

Таким образом, нет необходимости в уникальном индексе, но логика приложения должна заботиться об увеличении vid на вставке.

Удалить определенную версию:

update(
    { "_id":ObjectId("...") },
    { $pull:{ "data":{ "vid":2 } } }
)

Что это!

(помните о 16 МБ за лимит документа)

Ответ 5

Я работал над этим решением, которое содержит опубликованные, черновики и исторические версии данных:

{
  published: {},
  draft: {},
  history: {
    "1" : {
      metadata: <value>,
      document: {}
    },
    ...
  }
}

Далее я объясню модель: http://software.danielwatrous.com/representing-revision-data-in-mongodb/

Для тех, кто может реализовать что-то вроде этого в Java, вот пример:

http://software.danielwatrous.com/using-java-to-work-with-versioned-data/

Включая весь код, который вы можете использовать, если вам нравится

https://github.com/dwatrous/mongodb-revision-objects

Ответ 7

Другой вариант - использовать mongoose-history плагин.

let mongoose = require('mongoose');
let mongooseHistory = require('mongoose-history');
let Schema = mongoose.Schema;

let MySchema = Post = new Schema({
    title: String,
    status: Boolean
});

MySchema.plugin(mongooseHistory);
// The plugin will automatically create a new collection with the schema name + "_history".
// In this case, collection with name "my_schema_history" will be created.