Способы реализации контроля версий данных в MongoDB

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

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

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

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

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

    {
     '_id': 'новый идентификатор',
     'user': user_id,
     "отметка времени": отметка времени,
     'address_book_id': 'идентификатор записи адресной книги' 
     'old_record': {'first_name': 'Jon', 'last_name':'Doe' ...}
    }

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

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

10 ответов

Решение

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

  1. Diffs?
  2. Целые копии записей?

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

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

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

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

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

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

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

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

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

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

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

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

{
    _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 МБ на лимит документа)

Если вы ищете готовое решение -

Mongoid имеет простую версию

http://mongoid.org/en/mongoid/docs/extras.html

mongoid-history - плагин Ruby, который предоставляет значительно более сложное решение с проверкой, отменой и повторением

https://github.com/aq1018/mongoid-history

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

{
  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

Если вы используете mongoose, я обнаружил, что следующий плагин является полезной реализацией формата JSON Patch

Мангуст-патч-истории

Другой вариант - использовать плагин 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.

Я использовал приведенный ниже пакет для проекта meteor/MongoDB, и он хорошо работает, главное преимущество в том, что он хранит историю / ревизии в массиве в том же документе, поэтому нет необходимости в дополнительных публикациях или промежуточном программном обеспечении для доступа к истории изменений, Он может поддерживать ограниченное количество предыдущих версий (например, последние десять версий), а также поддерживает конкатенацию изменений (поэтому все изменения, произошедшие в течение определенного периода, будут охватываться одной ревизией).

nicklozon / Метеор-коллекция-пересмотры

Другой вариант звука - использовать Метеор Вермонго ( здесь)

Вы можете попробовать javers, я пока не нашел лучшего решения https://javers.org/

Попробуйте использовать Javers. Хорошая библиотека.

Другие вопросы по тегам