322

您能否分享您的想法,您将如何在 MongoDB 中实现数据版本控制。(我问过关于 Cassandra 的类似问题。如果您有任何想法哪个 db 更适合,请分享)

假设我需要对一个简单地址簿中的记录进行版本化。(通讯录记录存储为平面 json 对象)。我希望历史:

  • 将不经常使用
  • 将一次全部使用,以“时间机器”的方式呈现
  • 一条记录不会有超过几百个版本。历史不会过期。

我正在考虑以下方法:

  • 创建一个新的对象集合来存储记录的历史记录或对记录的更改。它会为每个版本存储一个对象,并引用地址簿条目。此类记录如下所示:

    {
     '_id': '新id',
     “用户”:用户 ID,
     “时间戳”:时​​间戳,
     'address_book_id': '通讯录记录的id'
     'old_record': {'first_name': 'Jon', 'last_name':'Doe' ...}
    }
    

    可以修改此方法以存储每个文档的版本数组。但这似乎是一种较慢的方法,没有任何优势。

  • 将版本存储为附加到地址簿条目的序列化 (JSON) 对象。我不确定如何将此类对象附加到 MongoDB 文档。也许作为一个字符串数组。(模仿使用 CouchDB 的简单文档版本控制

4

8 回答 8

163

深入研究时的第一个大问题是“你想如何存储变更集”

  1. 差异?
  2. 全记录副本?

我个人的方法是存储差异。因为这些差异的显示确实是一个特殊的动作,所以我会将差异放在不同的“历史”集合中。

我会使用不同的集合来节省内存空间。您通常不希望简单查询的完整历史记录。因此,通过将历史记录保留在对象之外,您还可以在查询该数据时将其保留在通常访问的内存之外。

为了让我的生活更轻松,我会制作一个包含时间戳差异的字典的历史文档。像这样的东西:

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

为了让我的生活变得非常轻松,我会将这部分作为我用来访问数据的 DataObjects(EntityWrapper,等等)。通常,这些对象具有某种形式的历史记录,因此您可以轻松地覆盖save()同时进行此更改的方法。

更新:2015-10

看起来现在有一个处理 JSON diffs 的规范。这似乎是一种更强大的存储差异/更改的方式。

于 2010-11-15T22:01:05.547 回答
33

有一个名为“Vermongo”的版本控制方案,它解决了其他回复中未处理的一些方面。

其中一个问题是并发更新,另一个是删除文档。

Vermongo 将完整的文档副本存储在影子集合中。对于某些用例,这可能会导致过多的开销,但我认为它也简化了许多事情。

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

于 2012-10-27T09:00:14.163 回答
28

这是另一种解决方案,对当前版本和所有旧版本使用单个文档:

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

data包含所有版本。data数组是有序的,新版本只会被$push编辑到数组的末尾。data.vid是版本 id,它是一个递增的数字。

获取最新版本:

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

通过以下方式获取特定版本vid

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

仅返回指定字段:

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

插入新版本:(并防止并发插入/更新)

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

2vid当前最新版本的,并且3是正在插入的新版本。因为您需要最新版本的vid,所以很容易获得下一个版本的vid: nextVID = oldVID + 1

条件将$and确保,即2是最新的vid

这样就不需要唯一索引,但应用程序逻辑必须负责增加vidon insert。

删除特定版本:

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

就是这样!

(记住每个文档 16MB 的限制)

于 2015-03-14T04:38:13.647 回答
14

如果您正在寻找现成的解决方案 -

Mongoid 内置了简单的版本控制

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

mongoid-history 是一个 Ruby 插件,它提供了一个非常复杂的解决方案,包括审计、撤消和重做

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

于 2012-08-11T00:23:12.970 回答
10

我完成了这个解决方案,该解决方案包含数据的已发布、草稿和历史版本:

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

我在这里进一步解释模型:http: //software.danielwatrous.com/representing-revision-data-in-mongodb/

对于那些可能在Java中实现类似这样的东西,这里有一个例子:

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

包括所有你可以分叉的代码,如果你喜欢的话

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

于 2013-07-09T15:42:06.013 回答
5

如果您使用的是猫鼬,我发现以下插件是JSON Patch格式的有用实现

猫鼬补丁历史

于 2017-02-10T16:10:51.893 回答
4

另一种选择是使用mongoose-history插件。

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.
于 2017-07-21T17:42:32.657 回答
1

我已将以下包用于流星/MongoDB 项目,它运行良好,主要优点是它将历史记录/修订存储在同一文档的数组中,因此无需额外的出版物或中间件来访问更改历史记录. 它可以支持有限数量的先前版本(例如最后十个版本),它还支持更改串联(因此在特定时期内发生的所有更改都将被一个修订版覆盖)。

nicklozon/meteor-collection-revisions

另一个声音选项是使用 Meteor Vermongo (这里)

于 2017-12-25T06:31:02.047 回答