MongoDB / NoSQL: Menyimpan Riwayat Perubahan Dokumen

134

Persyaratan yang cukup umum dalam aplikasi database adalah untuk melacak perubahan ke satu atau lebih entitas tertentu dalam database. Saya pernah mendengar ini disebut versi baris, tabel log atau tabel sejarah (saya yakin ada nama lain untuk itu). Ada beberapa cara untuk mendekatinya dalam RDBMS - Anda dapat menulis semua perubahan dari semua tabel sumber ke satu tabel (lebih dari satu log) atau memiliki tabel sejarah terpisah untuk setiap tabel sumber. Anda juga memiliki opsi untuk mengelola masuknya kode aplikasi atau melalui pemicu basis data.

Saya mencoba memikirkan seperti apa solusi untuk masalah yang sama akan terlihat seperti dalam database NoSQL / dokumen (khususnya MongoDB), dan bagaimana itu akan diselesaikan dengan cara yang seragam. Apakah sesederhana membuat nomor versi untuk dokumen, dan tidak pernah menimpa mereka? Membuat koleksi terpisah untuk dokumen "asli" vs. dokumen "dicatat"? Bagaimana hal ini memengaruhi permintaan dan kinerja?

Lagi pula, apakah ini skenario umum dengan database NoSQL, dan jika demikian, apakah ada solusi umum?

Phil Sandler
sumber
Driver bahasa apa yang Anda gunakan?
Joshua Partogi
Belum diputuskan - masih mengutak-atik dan bahkan belum menyelesaikan pilihan ujung belakang (meskipun MongoDB terlihat sangat ekstrem ). Saya telah bermain-main dengan NoRM (C #), dan saya suka beberapa nama yang terkait dengan proyek itu, jadi sepertinya sangat mungkin menjadi pilihan.
Phil Sandler
2
Saya tahu ini adalah pertanyaan lama tetapi bagi siapa saja yang mencari versi dengan MongoDB, pertanyaan SO ini terkait dan dalam pendapat saya dengan jawaban yang lebih baik.
AWolf

Jawaban:

107

Pertanyaan bagus, saya juga melihat ke dalam ini.

Buat versi baru pada setiap perubahan

Saya menemukan modul Versioning dari driver Mongoid untuk Ruby. Saya belum menggunakannya sendiri, tetapi dari apa yang saya temukan , itu menambahkan nomor versi ke setiap dokumen. Versi yang lebih lama tertanam dalam dokumen itu sendiri. Kelemahan utama adalah bahwa seluruh dokumen digandakan pada setiap perubahan , yang akan menghasilkan banyak konten duplikat disimpan ketika Anda berurusan dengan dokumen besar. Pendekatan ini baik-baik saja meskipun ketika Anda berurusan dengan dokumen berukuran kecil dan / atau tidak terlalu sering memperbarui dokumen.

Hanya simpan perubahan dalam versi baru

Pendekatan lain adalah dengan menyimpan hanya bidang yang diubah dalam versi baru . Kemudian Anda dapat 'meratakan' riwayat Anda untuk merekonstruksi versi dokumen apa pun. Ini agak rumit, karena Anda perlu melacak perubahan dalam model Anda dan menyimpan pembaruan dan menghapus dengan cara aplikasi Anda dapat merekonstruksi dokumen terbaru. Ini mungkin rumit, karena Anda berurusan dengan dokumen terstruktur daripada tabel SQL datar.

Simpan perubahan di dalam dokumen

Setiap bidang juga dapat memiliki riwayat individu. Rekonstruksi dokumen ke versi yang diberikan jauh lebih mudah dengan cara ini. Di aplikasi Anda, Anda tidak perlu melacak perubahan secara eksplisit, tetapi cukup buat versi baru dari properti saat Anda mengubah nilainya. Sebuah dokumen dapat terlihat seperti ini:

{
  _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" }
      ]
    }
  ]
}

Menandai bagian dari dokumen sebagai dihapus dalam versi masih agak canggung. Anda bisa memperkenalkan statebidang untuk bagian-bagian yang dapat dihapus / dipulihkan dari aplikasi Anda:

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

Dengan masing-masing pendekatan ini, Anda dapat menyimpan versi terbaru dan rata dalam satu koleksi dan data riwayat dalam koleksi terpisah. Ini akan meningkatkan waktu kueri jika Anda hanya tertarik pada versi terbaru dokumen. Tetapi ketika Anda membutuhkan versi terbaru dan data historis, Anda harus melakukan dua pertanyaan, bukan satu. Jadi pilihan untuk menggunakan satu koleksi vs. dua koleksi terpisah harus bergantung pada seberapa sering aplikasi Anda membutuhkan versi historis .

Sebagian besar dari jawaban ini hanyalah tumpukan pikiran saya, saya belum benar-benar mencoba semua ini. Melihat ke belakang, opsi pertama mungkin adalah solusi termudah dan terbaik, kecuali jika overhead data duplikat sangat signifikan untuk aplikasi Anda. Pilihan kedua cukup kompleks dan mungkin tidak sepadan dengan usaha. Opsi ketiga pada dasarnya adalah optimalisasi opsi dua dan harus lebih mudah diimplementasikan, tetapi mungkin tidak sepadan dengan upaya implementasi kecuali Anda benar-benar tidak bisa menggunakan opsi satu.

Menantikan umpan balik tentang ini, dan solusi orang lain untuk masalah ini :)

Niels van der Rest
sumber
Bagaimana dengan menyimpan delta di suatu tempat, sehingga Anda harus meratakan untuk mendapatkan dokumen historis dan selalu memiliki yang saat ini tersedia?
jpmc26
@ jpmc26 Itu mirip dengan pendekatan kedua, tetapi alih-alih menyimpan delta untuk mendapatkan versi terbaru, Anda menyimpan delta untuk sampai ke versi historis. Pendekatan mana yang digunakan tergantung pada seberapa sering Anda membutuhkan versi historisnya.
Niels van der Rest
Anda bisa menambahkan paragraf tentang menggunakan dokumen sebagai tampilan ke keadaan saat ini dan memiliki dokumen kedua sebagai changelog yang akan melacak setiap perubahan termasuk cap waktu (nilai awal harus muncul dalam log ini) - Anda kemudian dapat memutar ulang 'ke titik waktu tertentu dan misalnya menghubungkan apa yang sedang terjadi ketika algoritme Anda menyentuhnya atau melihat bagaimana suatu item ditampilkan ketika pengguna mengkliknya.
Manuel Arwed Schmidt
Apakah ini akan memengaruhi kinerja jika bidang yang diindeks diwakili sebagai array?
DmitriD
@ Semua - Bisakah Anda berbagi beberapa kode untuk mencapai ini?
Pra_A
8

Kami telah mengimplementasikan sebagian ini di situs kami dan kami menggunakan 'Revisi Store dalam dokumen terpisah "(dan database terpisah). Kami menulis fungsi khusus untuk mengembalikan diff dan kami menyimpannya. Tidak terlalu sulit dan dapat memungkinkan pemulihan otomatis.

Amala
sumber
2
Bisakah Anda membagikan beberapa kode yang sama? Pendekatan ini terlihat menjanjikan
Pra_A
1
@smilyface - Integrasi Spring Boot Javers adalah yang terbaik untuk mencapai ini
Pra_A
@PAA - Saya telah mengajukan pertanyaan (konsep yang hampir sama). stackoverflow.com/questions/56683389/... Apakah Anda punya input untuk itu?
smilyface
6

Mengapa tidak ada variasi pada perubahan Toko dalam dokumen ?

Alih-alih menyimpan versi terhadap setiap pasangan kunci, pasangan kunci saat ini dalam dokumen selalu mewakili keadaan terbaru dan 'log' perubahan disimpan dalam array sejarah. Hanya kunci-kunci yang telah berubah sejak pembuatan akan memiliki entri di log.

{
  _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" }
    }
  ]
}
Paul Taylor
sumber
2

Seseorang dapat memiliki database NoSQL saat ini dan database NoSQL historis. Akan ada ETL malam berjalan setiap hari. ETL ini akan merekam setiap nilai dengan cap waktu, jadi alih-alih nilai itu akan selalu tupel (bidang berversi). Itu hanya akan mencatat nilai baru jika ada perubahan yang dibuat pada nilai saat ini, menghemat ruang dalam proses. Sebagai contoh, file json database NoSQL database historis ini dapat terlihat seperti ini:

{
  _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" }
      ]
    }
  ]
}
Paul Kar.
sumber
0

Untuk pengguna Python (python 3+, dan tentu saja), ada HistoricalCollection yang merupakan perpanjangan dari objek Koleksi pymongo.

Contoh dari dokumen:

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'}]

Pengungkapan penuh, saya adalah pembuat paket. :)

Dash2TheDot
sumber