很好的问题。您确定了使用“规范化”数据模型(带有链接的不同文档类型)是最佳模型的几个非常重要的原因:
- 用户之间存在多对多关系 列表 游戏。
- 一对多关系很容易在单个文档中表示,该文档将容器用于“多”部分,但它们会变得很大,并且可能会出现并发冲突。
- 扩展单文档模型以存储多对多关系是站不住脚的。
- 一般来说,文档不变性非常适合并发系统。在 CouchDB 中,您完全按照您的说明执行此操作,通过存储表示图中边缘的“一次写入”文档,然后使用二级索引来重建您想要的链接部分并在单个链接中获取您想要的信息API 查询调用。
您也说得对,这里的解决方案是“map-side-join”(借用 hadoop 社区)。基本上,您希望在地图输出中使用不同的行来表示不同的信息。然后,您可以使用范围查询(startkey/endkey)来查询您需要的映射结果部分,以及,瞧,您的“连接”表的物化视图。但是,您在文档中没有找到的一个难题是:
3.2.3.1。链接文档
如果您的地图函数发出一个具有{'_id': XXX} 的对象值并且您使用include_docs=true 参数查询视图,那么CouchDB 将获取ID 为XXX 的文档,而不是被处理以发出键/值的文档对。
说明一切。这就是您如何取消引用指向您通过外键存储的链接文档的指针。然后将其与复合键(JS 数组的键)和view collation rules 结合使用。
以便我们对您的视图行进行排序:
["list_1"], null
["list_1", "game"], {"_id":"game_1234"}
["list_1", "game"], {"_id":"game_5678"}
["list_2"], null
["list_2","game"], {"_id":"game1234"}
["list_3"], null
...
将它与您现有的数据模型放在一起,这里有一些(未经测试的)伪代码应该可以解决问题:
function(doc) {
if (doc.type=="list") {
//this is the one in the one-to-many
emit( [doc._id]),);
}
else if (doc.type=="relationship") {
//this is the many in the one-to-many
//doc.list_id is our foreign key to the list. We use that as the key
//doc.game_id is the foreign key to the game. We use that as the value
emit( [doc.list_id,'game'], {'_id': doc.game_id});
}
}
最后,您将使用 startkey/endkey 进行查询,以便获得以您感兴趣的 list_id 开头的所有行。它看起来像:
curl -g 'https://usr:pwd@usr.cloudant.com/db/_design/design_doc_name/_view/view_name?startkey=["123"]&endkey=["123",{}]&include_docs=true'
-g 选项告诉 curl 不要使用 glob,这意味着您不必取消引用方括号等,include_docs=true 选项将跟随指向您使用 game_id 指定的外键的指针在relationship 文档中。
分析:
- 您正在使用本质上不可变的文档来存储状态更改,并让数据库为您计算聚合状态。这是一个可爱的大规模模型,也是我们最成功的模式之一。
- 添加或删除列表非常有效。
- 高并发下的出色扩展性能
- 在 Cloudant(和 CouchDB v2.0)中,我们还没有二级索引的“读你写”一致性。它在优先级列表中很高,但存在潜在的极端情况,在故障场景或高负载下,您可能看不到主索引和辅助索引之间的立即一致性。长话短说,仲裁用于主索引,但仲裁不是二级索引的可行模型,因此正在开发另一种一致性策略。