Dość powszechnym wymaganiem w aplikacjach baz danych jest śledzenie zmian w jednej lub większej liczbie określonych jednostek w bazie danych. Słyszałem, że nazywa się to wersjonowaniem wierszy, tabelą dziennika lub tabelą historii (jestem pewien, że istnieją inne nazwy). Istnieje wiele sposobów podejścia do tego w RDBMS - możesz zapisać wszystkie zmiany ze wszystkich tabel źródłowych w jednej tabeli (więcej dziennika) lub mieć oddzielną tabelę historii dla każdej tabeli źródłowej. Masz również możliwość zarządzania logowaniem w kodzie aplikacji lub za pomocą wyzwalaczy bazy danych.
Zastanawiam się, jak wyglądałoby rozwiązanie tego samego problemu w bazie danych NoSQL / dokumentów (w szczególności MongoDB) i jak można by to rozwiązać w jednolity sposób. Czy byłoby to tak proste, jak utworzenie numerów wersji dokumentów i nigdy ich nadpisywanie? Tworzysz oddzielne kolekcje dokumentów „rzeczywistych” i „zarejestrowanych”? Jak wpłynie to na zapytania i wydajność?
W każdym razie, czy jest to typowy scenariusz z bazami danych NoSQL, a jeśli tak, czy istnieje wspólne rozwiązanie?
Odpowiedzi:
Dobre pytanie, sam też się tym przyjrzałem.
Utwórz nową wersję przy każdej zmianie
Natknąłem się na moduł wersjonowania sterownika Mongoid dla Rubiego. Sam go nie używałem, ale z tego, co udało mi się znaleźć , dodaje numer wersji do każdego dokumentu. Starsze wersje są osadzone w samym dokumencie. Główną wadą jest to, że cały dokument jest duplikowany przy każdej zmianie , co spowoduje przechowywanie wielu zduplikowanych treści, gdy masz do czynienia z dużymi dokumentami. Takie podejście jest dobre, gdy masz do czynienia z dokumentami o małych rozmiarach i / lub nie aktualizujesz dokumentów zbyt często.
Przechowuj zmiany tylko w nowej wersji
Innym podejściem byłoby przechowywanie tylko zmienionych pól w nowej wersji . Następnie możesz „spłaszczyć” swoją historię, aby zrekonstruować dowolną wersję dokumentu. Jest to jednak dość złożone, ponieważ musisz śledzić zmiany w modelu oraz przechowywać aktualizacje i usuwać w taki sposób, aby aplikacja mogła zrekonstruować aktualny dokument. Może to być trudne, ponieważ masz do czynienia z dokumentami strukturalnymi zamiast płaskich tabel SQL.
Przechowuj zmiany w dokumencie
Każde pole może mieć również indywidualną historię. W ten sposób odtworzenie dokumentów do danej wersji jest znacznie łatwiejsze. W swojej aplikacji nie musisz jawnie śledzić zmian, po prostu utwórz nową wersję właściwości, gdy zmienisz jej wartość. Dokument mógłby wyglądać mniej więcej tak:
{ _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" } ] } ] }
Oznaczanie części dokumentu jako usuniętej w wersji jest jednak nadal nieco niewygodne. Możesz wprowadzić
state
pole dla części, które można usunąć / przywrócić z aplikacji:{ author: "xxx", body: [ { version: 4, value: "Spam" } ], state: [ { version: 4, deleted: false }, { version: 5, deleted: true } ] }
Przy każdym z tych podejść można przechowywać aktualną i spłaszczoną wersję w jednej kolekcji, a dane historyczne w osobnej kolekcji. Powinno to skrócić czas wykonywania zapytań, jeśli interesuje Cię tylko najnowsza wersja dokumentu. Ale gdy potrzebujesz zarówno najnowszej wersji, jak i danych historycznych, musisz wykonać dwa zapytania zamiast jednego. Zatem wybór użycia jednej kolekcji lub dwóch oddzielnych kolekcji powinien zależeć od tego, jak często aplikacja potrzebuje wersji historycznych .
Większość z tych odpowiedzi to tylko zrzut mózgu z moich myśli, właściwie jeszcze tego nie próbowałem. Patrząc wstecz, pierwsza opcja jest prawdopodobnie najłatwiejszym i najlepszym rozwiązaniem, chyba że obciążenie związane z duplikowaniem danych jest bardzo istotne dla aplikacji. Druga opcja jest dość złożona i prawdopodobnie nie jest warta wysiłku. Trzecia opcja jest w zasadzie optymalizacją opcji drugiej i powinna być łatwiejsza do wdrożenia, ale prawdopodobnie nie jest warta wysiłku wdrożeniowego, chyba że naprawdę nie możesz skorzystać z opcji pierwszej.
Czekam na opinie na ten temat i rozwiązania problemu przez innych :)
źródło
Częściowo zaimplementowaliśmy to w naszej witrynie i używamy „Wersji sklepu w oddzielnym dokumencie” (i osobnej bazie danych). Napisaliśmy niestandardową funkcję zwracającą różnice i ją przechowujemy. Nie jest to trudne i umożliwia automatyczne odzyskiwanie.
źródło
Dlaczego nie zmiana w sklepie zmienia się w dokumencie ?
Zamiast przechowywać wersje dla każdej pary kluczy, bieżące pary kluczy w dokumencie zawsze reprezentują najnowszy stan, a „dziennik” zmian jest przechowywany w tablicy historii. Tylko te klucze, które uległy zmianie od czasu utworzenia, będą miały wpis w dzienniku.
{ _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" } } ] }
źródło
Można mieć aktualną bazę danych NoSQL i historyczną bazę danych NoSQL. Codziennie będzie prowadzony nocny ETL. Ten ETL zarejestruje każdą wartość z sygnaturą czasową, więc zamiast wartości zawsze będą to krotki (pola wersjonowane). Nowa wartość zarejestruje tylko wtedy, gdy nastąpi zmiana wartości bieżącej, oszczędzając miejsce w procesie. Na przykład ten historyczny plik json bazy danych NoSQL może wyglądać następująco:
{ _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" } ] } ] }
źródło
Dla użytkowników Pythona (python 3+ i wyżej oczywiście) istnieje HistoricalCollection , będący rozszerzeniem obiektu Collection pymongo.
Przykład z dokumentów:
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": "[email protected]"}) users.patch_one({"username": "darth_later", "email": "[email protected]", "laser_sword_color": "red"}) list(users.revisions({"username": "darth_later"})) # [{'_id': ObjectId('5d98c3385d8edadaf0bb845b'), # 'username': 'darth_later', # 'email': '[email protected]', # '_revision_metadata': None}, # {'_id': ObjectId('5d98c3385d8edadaf0bb845b'), # 'username': 'darth_later', # 'email': '[email protected]', # '_revision_metadata': None, # 'laser_sword_color': 'red'}]
Pełne ujawnienie, jestem autorem pakietu. :)
źródło