ChatGPT解决这个技术问题 Extra ChatGPT

Ways to implement data versioning in MongoDB

Can you share your thoughts how would you implement data versioning in MongoDB. (I've asked similar question regarding Cassandra. If you have any thoughts which db is better for that please share)

Suppose that I need to version records in an simple address book. (Address book records are stored as flat json objects). I expect that the history:

will be used infrequently

will be used all at once to present it in a "time machine" fashion

there won't be more versions than few hundred to a single record. history won't expire.

I'm considering the following approaches:

Create a new object collection to store history of records or changes to the records. It would store one object per version with a reference to the address book entry. Such records would looks as follows: { '_id': 'new id', 'user': user_id, 'timestamp': timestamp, 'address_book_id': 'id of the address book record' 'old_record': {'first_name': 'Jon', 'last_name':'Doe' ...} } This approach can be modified to store an array of versions per document. But this seems to be slower approach without any advantages.

Store versions as serialized (JSON) object attached to address book entries. I'm not sure how to attach such objects to MongoDB documents. Perhaps as an array of strings. (Modelled after Simple Document Versioning with CouchDB)

I want to know if this has changed since the question was answered? I don't know much about oplog but was this around at the time, would it make a difference?
My approach is to think of all data as a time series.

C
Community

The first big question when diving in to this is "how do you want to store changesets"?

Diffs? Whole record copies?

My personal approach would be to store diffs. Because the display of these diffs is really a special action, I would put the diffs in a different "history" collection.

I would use the different collection to save memory space. You generally don't want a full history for a simple query. So by keeping the history out of the object you can also keep it out of the commonly accessed memory when that data is queried.

To make my life easy, I would make a history document contain a dictionary of time-stamped diffs. Something like this:

{
    _id : "id of address book record",
    changes : { 
                1234567 : { "city" : "Omaha", "state" : "Nebraska" },
                1234568 : { "city" : "Kansas City", "state" : "Missouri" }
               }
}

To make my life really easy, I would make this part of my DataObjects (EntityWrapper, whatever) that I use to access my data. Generally these objects have some form of history, so that you can easily override the save() method to make this change at the same time.

UPDATE: 2015-10

It looks like there is now a spec for handling JSON diffs. This seems like a more robust way to store the diffs / changes.


Wouldn't you worry that such History document (the changes object) will grow in time and updates become inefficient? Or does MongoDB handles document grow easily?
Take a look at the edit. Adding to changes is really easy: db.hist.update({_id: ID}, {$set { changes.12345 : CHANGES } }, true) This will perform an upsert that will only change the required data. Mongo creates documents with "buffer space" to handle this type of change. It also watches how documents in a collection change and modifies the buffer size for each collection. So MongoDB is designed for exactly this type of change (add new property / push to array).
I've done some testing and indeed the space reservation works pretty well. I wasn't able to catch the performance loss when the records were reallocated to the end of the data file.
You can use github.com/mirek/node-rus-diff to generate (MongoDB compatible) diffs for your history.
The JSON Patch RFC provides a way to express difffs. It has implementations in several languages.
D
David Pfeffer

There is a versioning scheme called "Vermongo" which addresses some aspects which haven't been dealt with in the other replies.

One of these issues is concurrent updates, another one is deleting documents.

Vermongo stores complete document copies in a shadow collection. For some use cases this might cause too much overhead, but I think it also simplifies many things.

https://github.com/thiloplanz/v7files/wiki/Vermongo


How do you actually use it?
There is no documentation on how this project is actually used. Is it something that lives withing Mongo somehow? It is a Java library? Is it merely a way of thinking about the problem? No idea and no hints are given.
This is actually a java app and the relavant code lives here: github.com/thiloplanz/v7files/blob/master/src/main/java/v7db/…
B
Benjamin M

Here's another solution using a single document for the current version and all old versions:

{
    _id: ObjectId("..."),
    data: [
        { vid: 1, content: "foo" },
        { vid: 2, content: "bar" }
    ]
}

data contains all versions. The data array is ordered, new versions will only get $pushed to the end of the array. data.vid is the version id, which is an incrementing number.

Get the most recent version:

find(
    { "_id":ObjectId("...") },
    { "data":{ $slice:-1 } }
)

Get a specific version by vid:

find(
    { "_id":ObjectId("...") },
    { "data":{ $elemMatch:{ "vid":1 } } }
)

Return only specified fields:

find(
    { "_id":ObjectId("...") },
    { "data":{ $elemMatch:{ "vid":1 } }, "data.content":1 }
)

Insert new version: (and prevent concurrent insert/update)

update(
    {
        "_id":ObjectId("..."),
        $and:[
            { "data.vid":{ $not:{ $gt:2 } } },
            { "data.vid":2 }
        ]
    },
    { $push:{ "data":{ "vid":3, "content":"baz" } } }
)

2 is the vid of the current most recent version and 3 is the new version getting inserted. Because you need the most recent version's vid, it's easy to do get the next version's vid: nextVID = oldVID + 1.

The $and condition will ensure, that 2 is the latest vid.

This way there's no need for a unique index, but the application logic has to take care of incrementing the vid on insert.

Remove a specific version:

update(
    { "_id":ObjectId("...") },
    { $pull:{ "data":{ "vid":2 } } }
)

That's it!

(remember the 16MB per document limit)


With mmapv1 storage, everytime a new version is added to data, there is a possibility that document will be moved.
Yes, that's right. But if you just add new versions every once in while, this should be neglectable.
s
s01ipsist

If you're looking for a ready-to-roll solution -

Mongoid has built in simple versioning

http://mongoid.org/en/mongoid/docs/extras.html#versioning

mongoid-history is a Ruby plugin that provides a significantly more complicated solution with auditing, undo and redo

https://github.com/aq1018/mongoid-history


for the ruby programming language.
D
Daniel Watrous

I worked through this solution that accommodates a published, draft and historical versions of the data:

{
  published: {},
  draft: {},
  history: {
    "1" : {
      metadata: <value>,
      document: {}
    },
    ...
  }
}

I explain the model further here: http://software.danielwatrous.com/representing-revision-data-in-mongodb/

For those that may implement something like this in Java, here's an example:

http://software.danielwatrous.com/using-java-to-work-with-versioned-data/

Including all the code that you can fork, if you like

https://github.com/dwatrous/mongodb-revision-objects


Awesome stuff :)
b
bmw15

If you are using mongoose, I have found the following plugin to be a useful implementation of the JSON Patch format

mongoose-patch-history


M
Muhammad Reda

Another option is to use mongoose-history plugin.

let mongoose = require('mongoose');
let mongooseHistory = require('mongoose-history');
let Schema = mongoose.Schema;

let MySchema = Post = new Schema({
    title: String,
    status: Boolean
});

MySchema.plugin(mongooseHistory);
// The plugin will automatically create a new collection with the schema name + "_history".
// In this case, collection with name "my_schema_history" will be created.

h
helcode

I have used the below package for a meteor/MongoDB project, and it works well, the main advantage is that it stores history/revisions within an array in the same document, hence no need for an additional publications or middleware to access change-history. It can support a limited number of previous versions (ex. last ten versions), it also supports change-concatenation (so all changes happened within a specific period will be covered by one revision).

nicklozon/meteor-collection-revisions

Another sound option is to use Meteor Vermongo (here)