MongoDB / NoSQL: Ведення історії змін документа


134

Досить поширеною вимогою в додатках бази даних є відстеження змін до одного або декількох конкретних об'єктів у базі даних. Я чув, як це називається версія версій, таблиця журналів або таблиця історії (я впевнений, що є інші назви для цього). Існує декілька способів наблизитись до неї в RDBMS - ви можете записати всі зміни з усіх вихідних таблиць в одну таблицю (більше журналу) або мати окрему таблицю історії для кожної вихідної таблиці. Також у вас є можливість керувати входом у код програми або через тригери бази даних.

Я намагаюся продумати, як би виглядало рішення тієї ж проблеми в базі даних NoSQL / документ (зокрема MongoDB), і як це було б вирішено рівномірно. Чи було б це так просто, як створювати номери версій для документів і ніколи не перезаписувати їх? Створення окремих колекцій для документів "реальних" проти "зареєстрованих"? Як це вплине на запити та ефективність?

У будь-якому випадку, це загальний сценарій з базами даних NoSQL, і якщо так, чи є спільне рішення?


Який мовний драйвер ви використовуєте?
Джошуа Партогі

Ще не вирішено - все ще хитрують і ще не доопрацювали вибір задніх кінців (хоча MongoDB виглядає надзвичайно вірогідним). Я розмовляв з NoRM (C #), і мені подобаються деякі імена, пов’язані з цим проектом, тому, здається, дуже ймовірно вибір.
Філ Сандлер

2
Я знаю, що це старе питання, але для тих, хто шукає версії з MongoDB, це питання пов'язане, і на мій погляд, з кращими відповідями.
AWolf

Відповіді:


107

Добре запитання, я також розглядав це.

Створіть нову версію щодо кожної зміни

Я натрапив на модуль версії драйвера 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 }
  ]
}

З кожним із цих підходів ви можете зберігати актуальну та вирівняну версію в одній колекції, а дані історії - в окремій колекції. Це має покращити час запитів, якщо вас цікавить лише остання версія документа. Але коли вам потрібні як остання версія, так і історичні дані, вам потрібно буде виконати два запити, а не один. Тож вибір використання однієї колекції порівняно з двома окремими колекціями повинен залежати від того, наскільки часто вашій програмі потрібні історичні версії .

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

З нетерпінням чекаю відгуків щодо цього та інших проблем вирішення проблеми :)


Що з дельтом зберігати дельти, щоб вам довелося вирівняти історичний документ і завжди мати наявний струм?
jpmc26

@ jpmc26 Це схоже на другий підхід, але замість того, щоб зберегти дельти, щоб дістатися до останніх версій, ви зберігаєте дельти, щоб дістатися до історичних версій. Який підхід використовувати, залежить від того, як часто вам знадобляться історичні версії.
Niels van der Rest

Ви можете додати абзац про використання документа як перегляду поточного стану речей та наявності другого документа як журналу змін, який буде відслідковувати кожну зміну, включаючи часову позначку (початкові значення повинні відображатися в цьому журналі) - ви можете потім 'повторити 'до будь-якого моменту часу і, наприклад, співвіднести те, що відбувалося, коли ваш алгоритм торкнувся його або побачити, як відображався елемент, коли користувач натискав на нього.
Мануель Арвед Шмідт

Чи вплине це на продуктивність, якщо індексовані поля представлені у вигляді масивів?
ДмитроD

@All - Чи можете ви поділитися кодом, щоб досягти цього?
Pra_A

8

Ми частково реалізували це на нашому веб-сайті та використовуємо "Редакції магазину в окремому документі" (і окрему базу даних). Ми написали спеціальну функцію для повернення розрізних даних, і ми зберігаємо це.


2
Чи можете ви поділитися кодом приблизно одного і того ж? Цей підхід виглядає багатообіцяючим
Pra_A

1
@smilyface - Найкраще для цього досягти інтеграції у весняні ботинки
Pra_A

@PAA - я задав питання (майже таке ж поняття). stackoverflow.com/questions/56683389/… Ви маєте для цього інформацію?
smilyface

6

Чому не змінюються зміни в магазині в документі ?

Замість того, щоб зберігати версії проти кожної пари ключів, поточні пари ключів у документі завжди представляють останнє стан, а "журнал" змін зберігається в масиві історії. Тільки ті ключі, які змінилися з моменту створення, матимуть запис у журналі.

{
  _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

Можна мати поточну базу даних 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" }
      ]
    }
  ]
}

0

Для користувачів Python (python 3+ і вище), є HistoricalCollection, що є розширенням об'єкта Collection Pymongo.

Приклад із документів:

from historical_collection.historical import HistoricalCollection
from pymongo import MongoClient
class Users(HistoricalCollection):
    PK_FIELDS = ['username', ]  # <<= This is the only requirement

# ...

users = Users(database=db)

users.patch_one({"username": "darth_later", "email": "darthlater@example.com"})
users.patch_one({"username": "darth_later", "email": "darthlater@example.com", "laser_sword_color": "red"})

list(users.revisions({"username": "darth_later"}))

# [{'_id': ObjectId('5d98c3385d8edadaf0bb845b'),
#   'username': 'darth_later',
#   'email': 'darthlater@example.com',
#   '_revision_metadata': None},
#  {'_id': ObjectId('5d98c3385d8edadaf0bb845b'),
#   'username': 'darth_later',
#   'email': 'darthlater@example.com',
#   '_revision_metadata': None,
#   'laser_sword_color': 'red'}]

Повне розкриття, я автор пакету. :)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.