好的,当你进入社交网站后,映入眼帘的唯一重要页面部分是你的活动流。活动流显示了你所有关注人的信息,这些信息是按最新时间排序的。每一条信息里都是网状结构,比如说图片,喜欢,分享以及评论。
用户有朋友,朋友有帖子,帖子有评论和喜欢,每一个评论有一个评论者,每一个喜欢有一个喜欢的人。这种关系并不比电视节目的复杂。和电视节目一样,当用户登录后,我们就想一次取出所有的数据。此外,在关系数据库中,所有的数据都是规格化的,这就得在7个表中查询才能得到所有数据。
7个表联合查询。啊!如果将每个用户的活动流作为一个大的非标准化的网状结构来存储的话,要比每一次连接查询看起来要好的多。
在2010年时,Diaspora团队做出了这个决定,并深受Etsy有关用文档结构存储文章的影响,尽管当时他们曾公开远离了MongoDB数据存储。同样的,Facebook的Cassandra也曾呼唤要远离关系数据库。Diaspora与时俱进的选择了MongoDB。从他们的信息数据来看,这样的选择是明智的。
问题可能出在哪?
Diaspora的“社交数据”和TV show的Mongo风格数据之间有一个很重要的不同点,我们在开始时都没有注意到。
TV Show中,数据关系表的每一部分都属于不同的数据类型。TV Show,Seasons,Episodes,Reviews,Cast members,他们各不相同。
但对于“社交数据”,有一部分具有相同的数据类型。事实上,图表中所有绿色部分所表示的都是同一种数据类型----Diaspora users
每个user具有一些friends,而每个friend可能自身就是一个user。或者说,他们也可能不是,因为这是一个分布式的系统。(我今天只是跳过了那整个层面的复杂性。)同样的,commenters和likers也可能是users。
这种类型的重复性使得要想将活动流正规化到一个单独的文档变得更难。那是因为在你的文档的不同部分,可能引用了相同的概念——在这个例子中,就是相同的user。在活动流中喜欢(like)那个帖子(post)的user,可能也是评论(comment)另一个不同帖子(post)的user。
重复数据重复数据
在MongoDB中我们可以用几种不同的方式来表示它。复制是一种简单的选择。在第一次提交的时候,friend的所有信息都被复制下来并被保存到like,之后在第二次提交的时候,一个单独的副本被保存到comment。这里的好处在于,在你需要数据的任何地方,它都是存在的,而且你仍然可以把整个活动流作为一个单独的文档处理。
这里就是这类完全非规范化的流程文档的样子。
这里有内联的user数据的拷贝。这个是Joe的数据流,而且在最顶级有他的用户数据,包括他的name和URL。紧接着下来是他的数据流,包含有Jane的帖子(post)。Joe喜欢(like)Jane的帖子,所以在Jane的帖子的喜欢者(likes)中,我们有Joe的数据的一个单独的拷贝。
你会明白为什么这样做很有吸引力:所有你需要的数据在你需要的地方已经存在。
你也会同样明白为什么这么做是危险的。更新一个user的数据,就意味着要查找所有他们出现过的活动流,以便在这些不同的地方更新这个数据。这很容易出错,经常导致不一致的数据和奇怪的错误,特别是在处理删除操作的时候。