MongoDB / NoSQL: Belge Değişiklik Geçmişini Tutma


134

Veritabanı uygulamalarında oldukça yaygın bir gereklilik, bir veritabanındaki bir veya daha fazla belirli varlıkta yapılan değişiklikleri izlemektir. Bunu satır versiyonlama, bir günlük tablosu veya bir geçmiş tablosu olarak adlandırdığını duydum (bunun için başka isimler olduğundan eminim). Bir RDBMS'de buna yaklaşmanın birkaç yolu vardır - tüm kaynak tablolardaki tüm değişiklikleri tek bir tabloya (bir günlükten daha fazla) yazabilir veya her kaynak tablo için ayrı bir geçmiş tablosuna sahip olabilirsiniz. Ayrıca, uygulama kodunu veya veritabanı tetikleyicileri aracılığıyla oturum açmayı yönetme seçeneğiniz de vardır.

Bir NoSQL / belge veritabanında (özellikle MongoDB) aynı soruna bir çözümün nasıl görüneceğini ve bunun tek tip bir şekilde nasıl çözüleceğini düşünmeye çalışıyorum. Belgeler için sürüm numaraları oluşturmak ve bunların üzerine asla yazmamak kadar basit mi? "Gerçek" ve "kayıtlı" belgeler için ayrı koleksiyonlar mı oluşturuyorsunuz? Bu sorgulama ve performansı nasıl etkiler?

Her neyse, bu NoSQL veritabanlarında yaygın bir senaryo mu ve öyleyse, ortak bir çözüm var mı?


Hangi dil sürücüsünü kullanıyorsunuz?
Joshua Partogi

Hala müdahalesi ve (MongoDB görünüyor olsa bile henüz geri uçlarının seçim kesinleşmiş değil - Henüz karar Extrememly olasılıkla). NoRM (C #) ile uğraşıyorum ve bu projeyle ilişkili isimlerden bazılarını beğeniyorum, bu yüzden büyük olasılıkla seçim bu.
Phil Sandler

2
Bunun eski bir soru olduğunu biliyorum, ancak MongoDB ile sürüm oluşturma arayan herkes için, bu SO sorusu ilgili ve benim görüşüme göre daha iyi cevaplarla.
AWolf

Yanıtlar:


107

Güzel soru, ben de bununla ilgileniyordum.

Her değişiklikte yeni bir sürüm oluşturun

Ruby için Mongoid sürücüsünün Sürüm Oluşturma modülüne rastladım . Kendim kullanmadım, ancak bulabildiğim kadarıyla her belgeye bir sürüm numarası ekliyor. Daha eski sürümler belgenin kendisinde gömülüdür. En büyük dezavantajı, her değişiklikte tüm belgenin kopyalanmasıdır ; bu, büyük belgelerle uğraşırken çok sayıda yinelenen içeriğin depolanmasına neden olur. Bu yaklaşım, küçük boyutlu belgelerle uğraşırken ve / veya belgeleri çok sık güncellemediğinizde iyidir.

Değişiklikleri yalnızca yeni bir sürümde saklayın

Başka bir yaklaşım, yeni bir sürümde yalnızca değiştirilen alanları depolamak olacaktır . Ardından, belgenin herhangi bir sürümünü yeniden oluşturmak için geçmişinizi 'düzleştirebilirsiniz'. Modelinizdeki değişiklikleri izlemeniz ve güncellemeleri ve silmeleri, uygulamanızın güncel belgeyi yeniden yapılandırabileceği şekilde depolamanız gerektiğinden, bu oldukça karmaşıktır. Düz SQL tabloları yerine yapılandırılmış belgelerle uğraştığınız için bu biraz yanıltıcı olabilir.

Belgede değişiklikleri saklayın

Her alanın ayrıca ayrı bir geçmişi olabilir. Belgeleri belirli bir sürüme yeniden yapılandırmak bu şekilde çok daha kolaydır. Uygulamanızda değişiklikleri açıkça izlemeniz gerekmez, ancak değerini değiştirdiğinizde mülkün yeni bir sürümünü oluşturmanız yeterlidir. Bir belge şunun gibi görünebilir:

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

Bir sürümde belgenin bir bölümünü silinmiş olarak işaretlemek yine de biraz garip. stateUygulamanızdan silinebilecek / geri yüklenebilecek parçalar için bir alan tanıtabilirsiniz :

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

Bu yaklaşımların her biri ile güncel ve düzleştirilmiş bir sürümü tek bir koleksiyonda ve geçmiş verilerini ayrı bir koleksiyonda depolayabilirsiniz. Bu, yalnızca bir belgenin son sürümüyle ilgileniyorsanız, sorgu sürelerini iyileştirmelidir. Ancak hem en son sürüme hem de geçmiş verilere ihtiyacınız olduğunda, bir yerine iki sorgu gerçekleştirmeniz gerekir. Dolayısıyla, iki ayrı koleksiyon yerine tek bir koleksiyon kullanma seçimi, uygulamanızın geçmiş sürümlere ne sıklıkla ihtiyaç duyduğuna bağlı olmalıdır .

Bu cevabın çoğu düşüncelerimin beyin dökümüdür, aslında bunların hiçbirini henüz denemedim. Geriye dönüp baktığımızda, yinelenen verilerin ek yükü uygulamanız için çok önemli olmadığı sürece, ilk seçenek muhtemelen en kolay ve en iyi çözümdür. İkinci seçenek oldukça karmaşıktır ve muhtemelen çabaya değmez. Üçüncü seçenek temelde ikinci seçeneğin bir optimizasyonudur ve uygulaması daha kolay olmalıdır, ancak birinci seçeneği gerçekten uygulayamadığınız sürece muhtemelen uygulama çabasına değmez.

Bununla ilgili geri bildirimleri ve diğer kişilerin soruna yönelik çözümlerini dört gözle bekliyorum :)


Deltaları bir yerde depolamaya ne dersiniz, böylece tarihsel bir belge elde etmek için düzleştirmeniz ve her zaman mevcut olana sahip olmanız gerekir?
jpmc26

@ jpmc26 Bu ikinci yaklaşıma benzer, ancak deltaları en son sürümlere ulaşmak için kaydetmek yerine, geçmiş sürümlere ulaşmak için deltaları kaydediyorsunuz. Hangi yaklaşımın kullanılacağı, geçmiş sürümlere ne sıklıkla ihtiyaç duyacağınıza bağlıdır.
Dinlenme der Niels van

Belgeyi mevcut duruma bir görünüm olarak kullanma ve bir zaman damgası da dahil olmak üzere her değişikliği takip edecek bir değişiklik günlüğü olarak ikinci bir belgeye sahip olma hakkında bir paragraf ekleyebilirsiniz (başlangıç ​​değerleri bu günlükte görünmelidir) - daha sonra 'yeniden oynatabilirsiniz 'zaman içinde herhangi bir noktaya ve örneğin, algoritmanız ona dokunduğunda neler olup bittiğini ilişkilendirin ya da kullanıcı üzerine tıkladığında bir öğenin nasıl görüntülendiğini görün.
Manuel Arwed Schmidt

Dizine alınmış alanlar diziler olarak temsil edilirse, bu performansı etkiler mi?
DmitriD

@All - Bunu başarmak için lütfen biraz kod paylaşır mısınız?
Pra_A

8

Bunu sitemizde kısmen uyguladık ve "Mağaza Revizyonlarını ayrı bir belgede" (ve ayrı bir veritabanında) kullanıyoruz. Farkları döndürmek için özel bir işlev yazdık ve saklıyoruz. O kadar zor değil ve otomatik kurtarmaya izin verebiliriz.


2
Lütfen aynı şekilde biraz kod paylaşır mısınız? Bu yaklaşım umut verici görünüyor
Pra_A

1
@smilyface - Spring Boot Javers entegrasyonu bunu başarmak için en iyisidir
Pra_A

@PAA - Bir soru sordum (neredeyse aynı kavram). stackoverflow.com/questions/56683389/… Bunun için herhangi bir girdiniz var mı?
smilyface

6

Belgedeki Mağaza değişikliklerinde neden bir değişiklik olmasın? ?

Sürümleri her anahtar çiftine karşı depolamak yerine, belgedeki geçerli anahtar çiftleri her zaman en son durumu temsil eder ve bir geçmiş dizisi içinde bir değişiklik 'günlüğü' depolanır. Yalnızca oluşturulduktan sonra değişen anahtarların günlükte bir girişi olacaktır.

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

Mevcut bir NoSQL veritabanı ve tarihsel bir NoSQL veritabanı olabilir. Her gün her gece ETL çalışması yapılacak. Bu ETL, her değeri bir zaman damgasıyla kaydedecektir, bu nedenle değerler yerine her zaman tuple (sürümlü alanlar) olacaktır. Yalnızca mevcut değerde bir değişiklik yapıldığında yeni bir değer kaydedecek ve bu süreçte yer tasarrufu sağlayacaktır. Örneğin, bu tarihsel NoSQL veritabanı json dosyası şöyle görünebilir:

{
  _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 kullanıcıları (ve tabii ki yukarı piton 3+) için, var HistoricalCollection pymongo en Koleksiyonu nesnenin bir uzantısıdır.

Dokümanlardan örnek:

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

Tam açıklama, ben paket yazarıyım. :)

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.