MongoDB / NoSQL: ведение истории изменений документа - PullRequest
113 голосов
/ 18 августа 2010

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

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

В любом случае, это общий сценарий с базами данных NoSQL, и если да, то есть ли общее решение?

Ответы [ 4 ]

92 голосов
/ 18 августа 2010

Хороший вопрос, я тоже сам разбирался в этом.

Создать новую версию при каждом изменении

Я наткнулся на модуль управления версиями драйвера Mongoid для Ruby. Я не использовал его сам, но из того, что я смог найти , он добавляет номер версии к каждому документу. Старые версии встроены в сам документ. Основным недостатком является то, что весь документ дублируется при каждом изменении , что приводит к сохранению большого количества дублирующегося содержимого при работе с большими документами. Этот подход хорош, когда вы имеете дело с документами небольшого размера и / или не очень часто обновляете документы.

Сохранять только изменения в новой версии

Другой подход заключается в сохранении только измененных полей в новой версии . Затем вы можете «сгладить» свою историю, чтобы восстановить любую версию документа. Это довольно сложно, так как вам нужно отслеживать изменения в вашей модели и хранить обновления и удаления таким образом, чтобы ваше приложение могло восстановить обновленный документ. Это может быть сложно, поскольку вы имеете дело со структурированными документами, а не с плоскими таблицами SQL.

Сохранение изменений в документе

Каждое поле также может иметь индивидуальную историю. Реконструировать документы по заданной версии намного проще. В вашем приложении вам не нужно явно отслеживать изменения, а просто создавать новую версию свойства при изменении его значения. Документ может выглядеть примерно так:

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { version: 1, value: "Hello world" },
    { version: 6, value: "Foo" }
  ],
  body: [
    { version: 1, value: "Is this thing on?" },
    { version: 2, value: "What should I write?" },
    { version: 6, value: "This is the new body" }
  ],
  tags: [
    { version: 1, value: [ "test", "trivial" ] },
    { version: 6, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { version: 3, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { version: 4, value: "Spam" },
        { version: 5, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { version: 7, value: "Not bad" },
        { version: 8, value: "Not bad at all" }
      ]
    }
  ]
}

Отметить часть документа как удаленную в версии все еще несколько неудобно. Вы можете ввести поле state для деталей, которые можно удалить / восстановить из вашего приложения:

{
  author: "xxx",
  body: [
    { version: 4, value: "Spam" }
  ],
  state: [
    { version: 4, deleted: false },
    { version: 5, deleted: true }
  ]
}

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

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

Ждем отзывов об этом и решениях других людей:)

7 голосов
/ 19 августа 2010

Мы частично реализовали это на нашем сайте, и мы используем «Хранить ревизии в отдельном документе» (и в отдельной базе данных). Мы написали пользовательскую функцию для возврата различий, и мы храним это. Не так сложно и можно учесть автоматическое восстановление.

4 голосов
/ 20 августа 2016

Почему бы не вариант Сохранить изменения в документе ?

Вместо сохранения версий для каждой пары ключей, текущие пары ключей в документе всегда представляют самое последнее состояние и«журнал» изменений хранится в массиве истории.Только те ключи, которые были изменены с момента создания, будут иметь запись в журнале.

{
  _id: "4c6b9456f61f000000007ba6"
  title: "Bar",
  body: "Is this thing on?",
  tags: [ "test", "trivial" ],
  comments: [
    { key: 1, author: "joe", body: "Something cool" },
    { key: 2, author: "xxx", body: "Spam", deleted: true },
    { key: 3, author: "jim", body: "Not bad at all" }
  ],
  history: [
    { 
      who: "joe",
      when: 20160101,
      what: { title: "Foo", body: "What should I write?" }
    },
    { 
      who: "jim",
      when: 20160105,
      what: { tags: ["test", "test2"], comments: { key: 3, body: "Not baaad at all" }
    }
  ]
}
2 голосов
/ 09 июля 2016

Можно иметь текущую базу данных NoSQL и историческую базу данных NoSQL. Там будет ночной ETL, запускаемый каждый день. Этот ETL будет записывать каждое значение с отметкой времени, поэтому вместо значений это всегда будут кортежи (версионные поля). Он будет записывать новое значение только в том случае, если в текущее значение были внесены изменения, что экономит место в процессе. Например, этот исторический json-файл базы данных NoSQL может выглядеть так:

{
  _id: "4c6b9456f61f000000007ba6"
  title: [
    { date: 20160101, value: "Hello world" },
    { date: 20160202, value: "Foo" }
  ],
  body: [
    { date: 20160101, value: "Is this thing on?" },
    { date: 20160102, value: "What should I write?" },
    { date: 20160202, value: "This is the new body" }
  ],
  tags: [
    { date: 20160101, value: [ "test", "trivial" ] },
    { date: 20160102, value: [ "foo", "test" ] }
  ],
  comments: [
    {
      author: "joe", // Unversioned field
      body: [
        { date: 20160301, value: "Something cool" }
      ]
    },
    {
      author: "xxx",
      body: [
        { date: 20160101, value: "Spam" },
        { date: 20160102, deleted: true }
      ]
    },
    {
      author: "jim",
      body: [
        { date: 20160101, value: "Not bad" },
        { date: 20160102, value: "Not bad at all" }
      ]
    }
  ]
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...