什么是在关键值存储中保存带有修订的文档的最佳方法?

时间:2011-04-08 12:22:09

标签: node.js mongodb key-value nosql

我是Key-Value Stores的新手,我需要你的建议。我们正在开发一个管理文档及其修订的系统。有点像维基。我们正在考虑将这些数据保存在一个关键值存储中。

请不要给我一个您喜欢的数据库的建议,因为我们想要破解它,以便我们可以使用许多不同的键值数据库。我们正在使用node.js,因此我们可以轻松使用json。

我的问题是:数据库的结构应该是什么样的?我们有每个文档的元数据(timestamp,lasttext,id,latestrevision),我们有每个版本的数据(更改,作者,时间戳等...)。那么,你推荐哪种键/值结构?

THX

3 个答案:

答案 0 :(得分:5)

来自MongoDB的groups。它有些特定于MongoDB,但它非常通用。

这些历史实施大多分为两种常见策略。

策略1:嵌入历史

理论上,您可以将文档的历史记录嵌入到文档本身中。这甚至可以原子地完成。

> db.docs.save( { _id : 1, text : "Original Text" } ) 
> var doc = db.docs.findOne() 
> db.docs.update( {_id: doc._id}, { $set : { text : 'New Text' }, $push : { hist : doc.text } } ) 
> db.docs.find() 
{ "_id" : 1, "hist" : [ "Original Text" ], "text" : "New Text" } 

策略2:将历史记录写入单独的集合

> db.docs.save( { _id : 1, text : "Original Text" } ) 
> var doc = db.docs.findOne() 
> db.docs_hist.insert ( { orig_id : doc._id, ts : Math.round((new Date()).getTime() / 1000), data : doc } ) 
> db.docs.update( {_id:doc._id}, { $set : { text : 'New Text' }  } ) 

在这里你会看到我做了两次写作。一个到主集合和 一个到历史收藏。 要获得快速历史记录查找,只需抓取原始ID:

> db.docs_hist.ensureIndex( { orig_id : 1, ts : 1 }) 
> db.docs_hist.find( { orig_id : 1 } ).sort( { ts : -1 } )

  • 只有显示差异
  • 才能增强这两种策略
  • 您可以通过添加history collectionoriginal collection
  • 的链接进行混合
  

在密钥值存储中保存包含修订的文档的最佳方法是什么?

很难说有一种“最佳方式”。显然这里有一些权衡取舍。

嵌入:

  • 单个文档的原子更改
  • 会导致大型文档,可能会破坏合理的大小限制
  • 可能必须增强代码以避免在没有必要时返回完整的文档

单独收集:

  • 更容易编写查询
  • 不是原子的,需要两个操作(你有交易吗?
  • 更多存储空间(原始文档上的额外索引

答案 1 :(得分:1)

我会在附加修订数据的每个文档下保留实际数据的层次结构,例如:

{ 
  [
    {
      "timestamp" : "2011040711350621",
      "data" : { ... the real data here .... }
    },
    {
      "timestamp" : "2011040711350716",
      "data" : { ... the real data here .... }
    }
  ]
}

然后使用推送操作添加新版本并定期删除旧版本。您可以使用最后(或第一个)过滤器仅在任何给定时间获取最新副本。

答案 2 :(得分:1)

我认为有多种方法,这个问题已经过时了,但我会在今年早些时候的工作中给出两分钱。我一直在使用MongoDB。

在我的情况下,我有一个用户帐户,然后在不同的社交网络上有个人资料。我们希望跟踪社交网络配置文件的更改并希望修改它们,以便我们创建两个结构来测试。两种方法都有一个指向外部对象的User对象。我们不想从一开始就嵌入对象。

用户看起来像:

User {
  "tags"              : [Tags]
  "notes"             : "Notes"
  "facebook_profile"  : <combo_foreign_key>
  "linkedin_profile"  : <same as above>
}

然后,对于combo_foreign_key,我们使用了这种模式(为简单起见使用Ruby插值语法)

combo_foreign_key = "#{User.key}__#{new_profile.last_updated_at}"

facebook_profiles {
  combo_foreign_key: facebook_profile
  ... and you keep adding your foreign objects in this pattern
}

这使我们O(1)查找用户的最新FacebookProfile,但要求我们保留最新的FK存储在User对象中。如果我们想要所有的FacebookProfiles,那么我们会在facebook_profiles集合中询问前缀为“#{User.key} __”的所有密钥,这是O(N)......

我们尝试的第二个策略是在User对象上存储这些FacebookProfile键的数组,以便User对象的结构从

更改
  "facebook_profile"  : <combo_foreign_key>

  "facebook_profile"  : [<combo_foreign_key>]

在我们添加新的配置文件变体时,我们只需添加新的combo_key。然后我们只是快速排序“facebook_profile”属性和最大的索引来获取我们最新的个人资料副本。此方法必须对M个字符串进行排序,然后根据该排序列表中的最大项目对FacebookProfile进行索引。获取最新版本的速度稍微慢了一些,但它让我们有机会了解每个版本的用户FacebookProfile,我们不必担心确保foreign_key真的是最新的配置文件对象。

起初我们的修订版计数非常小,而且它们都运行良好。我想我现在比第二个更喜欢第一个。

他们会非常喜欢别人对解决这个问题的看法。在另一个答案中提出的GIT想法实际上听起来非常巧妙,因为我的用例会很好用......很酷。