在MongoDB中实现数据版本的方法
你能分享一下你的想法吗?你将如何在MongoDB中实现数据版本控制? (我问过关于Cassandra的类似问题,如果你有任何想法,哪个db更好,请分享)
假设我需要在一个简单的地址簿中logging版本。 (地址簿logging存储为扁平json对象)。 我期望的历史:
- 将很less使用
- 将被一次全部使用,以“时间机器”的方式呈现
- 不会有更多的版本比几百到一个单一的logging。 历史不会过期。
我正在考虑以下方法:
-
创build一个新的对象集合来存储logging的历史logging或更改logging。 它将在每个版本中存储一个对象,并引用地址簿条目。 这样的logging看起来如下:
{ '_id':'new id', 'user':user_id, '时间戳':时间戳, 'address_book_id':'地址簿logging的ID' 'old_record':{'first_name':'Jon','last_name':'Doe'...} }
这种方法可以修改为每个文档存储一个版本的数组。 但是这似乎是没有任何优势的较慢的方法。
-
将版本存储为附加到地址簿条目的序列化(JSON)对象。 我不确定如何将这些对象附加到MongoDB文档。 也许作为一个string的数组。 ( 使用CouchDB进行简单文档版本控制后进行build模 )
第一个大问题是, “如何存储变更集” ?
- 比较速度?
- 整个logging副本?
我个人的做法是存储差异。 因为显示这些差异确实是一个特殊的行为,所以我会把差异放在不同的“历史”集合中。
我会使用不同的集合来节省内存空间。 您通常不需要完整的历史logging来进行简单的查询。 因此,通过将历史logging保留在对象之外,可以在查询数据时将其保留在通常访问的内存之外。
为了让我的生活变得简单,我将使历史文档包含时间戳差异字典。 像这样的东西:
{ _id : "id of address book record", changes : { 1234567 : { "city" : "Omaha", "state" : "Nebraska" }, 1234568 : { "city" : "Kansas City", "state" : "Missouri" } } }
为了让我的生活变得很简单,我将使用DataObjects(EntityWrapper,不pipe)来访问我的数据。 通常这些对象具有某种forms的历史,所以你可以很容易地重写save()
方法来同时进行这个改变。
更新:2015-10
看起来现在有一个处理JSON差异的规范 。 这似乎是一个更强大的方式来存储差异/变化。
有一个名为“Vermongo”的版本控制scheme,解决了其他答复中尚未处理的一些方面。
其中一个问题是并发更新,另一个是删除文件。
Vermongo将完整的文档副本存储在一个影子集合中。 对于一些使用情况,这可能会造成太多的开销,但我认为这也简化了很多事情。
如果你正在寻找一个现成的解决scheme –
Mongoid已经build立了简单的版本控制
http://mongoid.org/en/mongoid/docs/extras.html#versioning
mongoid-history是一个Ruby插件,通过审计,撤消和重做提供了一个更为复杂的解决scheme
这是另一个使用单个文档的当前版本和所有旧版本的解决scheme:
{ _id: ObjectId("..."), data: [ { vid: 1, content: "foo" }, { vid: 2, content: "bar" } ] }
data
包含所有版本。 data
数组是有序的 ,新版本只会得到$push
到数组的末尾。 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
是当前最新版本的vid
, 3
是插入的新版本。 因为你需要最新版本的vid
,很容易获得下一个版本的vid
: nextVID = oldVID + 1
。
$and
condition将确保2
是最新的vid
。
这样就不需要唯一的索引,但是应用程序逻辑必须在插入时关注增加vid
。
删除特定的版本:
update( { "_id":ObjectId("...") }, { $pull:{ "data":{ "vid":2 } } } )
而已!
(请记住每个文档限制16MB)
我通过这个解决scheme来处理数据的已发布,草案和历史版本:
{ 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/
包括所有你可以分叉的代码,如果你喜欢的话
如果您正在使用mongoose,我发现以下插件是JSON补丁格式的有用实现
mongoose补丁历史
另一个select是使用mongoose历史插件。
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.