在 MongoDB 中实现数据版本控制的方法

Posted

技术标签:

【中文标题】在 MongoDB 中实现数据版本控制的方法【英文标题】:Ways to implement data versioning in MongoDB 【发布时间】:2011-05-10 06:20:43 【问题描述】:

您能否分享您的想法,您将如何在 MongoDB 中实现数据版本控制。 (我已经问过similar question regarding Cassandra。如果您有任何想法哪个数据库更好,请分享)

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

不常使用 将一次性全部用于以“时间机器”的方式呈现它 单个记录的版本不会超过几百个。 历史不会过期。

我正在考虑以下方法:

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

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

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

将版本存储为附加到地址簿条目的序列化 (JSON) 对象。我不确定如何将此类对象附加到 MongoDB 文档。也许作为一个字符串数组。 (Modelled after Simple Document Versioning with CouchDB)

【问题讨论】:

我想知道问题得到解答后情况是否发生了变化?我对 oplog 了解不多,但当时有这种情况吗,会有所不同吗? 我的方法是将所有数据视为时间序列。 【参考方案1】:

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

    有区别吗? 整个记录副本?

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

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

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


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

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

更新:2015-10

现在好像有a spec for handling JSON diffs。这似乎是一种更可靠的方式来存储差异/更改。

【讨论】:

你不担心这样的历史文档(更改对象)会随着时间的推移而增长,更新变得低效吗?或者 MongoDB 处理文档是否容易增长? 看看编辑。添加到changes 非常简单:db.hist.update(_id: ID, $set changes.12345 : CHANGES , true) 这将执行一个只会更改所需数据的更新插入。 Mongo 创建带有“缓冲空间”的文档来处理这种类型的变化。它还监视集合中的文档如何更改并修改每个集合的缓冲区大小。所以 MongoDB 正是为这种类型的变化而设计的(添加新属性/推送到数组)。 我做了一些测试,确实空间保留工作得很好。当记录被重新分配到数据文件的末尾时,我无法捕捉到性能损失。 您可以使用github.com/mirek/node-rus-diff 为您的历史生成(MongoDB 兼容)差异。 JSON Patch RFC 提供了一种表达差异的方法。它有implementations in several languages。【参考方案2】:

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

Mongoid 内置了简单的版本控制

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

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

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

【讨论】:

用于 ruby​​ 编程语言。【参考方案3】:

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

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

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

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

【讨论】:

你是如何实际使用它的? 没有关于如何实际使用这个项目的文档。它以某种方式与Mongo一起生活吗?它是一个Java库吗?它仅仅是一种思考问题的方式吗?没有任何想法,也没有给出任何提示。 这实际上是一个 java 应用程序,相关代码在这里:github.com/thiloplanz/v7files/blob/master/src/main/java/v7db/…【参考方案4】:

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


  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

【讨论】:

很棒的东西 :)【参考方案5】:

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


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

data 包含所有版本。 data 数组是有序的,新版本只会将$pushed 放到数组的末尾。 data.vid 是版本号,是一个递增的数字。

获取最新版本:

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"   
)

2 是当前最新版本的vid3 是插入的新版本。因为你需要最新版本的vid,所以很容易得到下一个版本的vidnextVID = oldVID + 1

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

这种方式不需要唯一索引,但应用程序逻辑必须注意在插入时增加 vid

删除特定版本:

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

就是这样!

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

【讨论】:

使用 mmapv1 存储,每次向数据中添加新版本时,都有可能移动文档。 是的,没错。但如果你只是偶尔添加新版本,这应该可以忽略不计。【参考方案6】:

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

mongoose-patch-history

【讨论】:

【参考方案7】:

另一种选择是使用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.

【讨论】:

【参考方案8】:

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

nicklozon/meteor-collection-revisions

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

【讨论】:

以上是关于在 MongoDB 中实现数据版本控制的方法的主要内容,如果未能解决你的问题,请参考以下文章

如何使用带有 has_many 的 PaperTrail 版本控制:通过在 Rails 4 中实现关联

代码的自动语义版本控制

在所有 Android 版本中实现选项卡

在MongoDB中实现聚合函数

在mongodb中实现分页

如何在Mongodb中实现数据超时自动删除功能?