使用基于文档的nosql (mongodb、couchdb和riak等)查询关系数据的性能



为了跟进我关于用nosql建模关系数据的问题,我阅读了几篇关于这个主题的文章:

Nosql不代表非关系

Nosql电子商务示例

他们似乎暗示nosql可以处理规范化的关系数据。

那么让我们继续我之前的例子,一个CMS系统有两种类型的数据:文章和作者,其中文章有一个引用(通过ID)到作者。

系统需要支持的操作如下:

  1. 通过id和作者获取文章
  2. 获取指定作者的所有文章
  3. 查找作者按创建日期排序的前10篇文章

我想了解这些操作的性能,当比较相同的操作,如果相同的数据存储在RDBMS上。特别是,请指定操作是否使用MapReduce,是否需要多次访问nosql存储(链接),或者预连接

我想限制讨论基于文档的 nosql解决方案,如mongodb, couchdb和riak。

编辑1:

Spring-data项目在Riak和Mongodb上可用

只是想为任何可能好奇的人抛出一个CouchDB答案。:)

正如上面的第一个答案所提到的,将作者文档嵌入到文章文档中是不明智的,因此下面的示例假设了两种文档类型:文章和作者。

CouchDB通常使用JavaScript编写的MapReduce查询(但Python, Ruby, Erlang和其他可用)。MapReduce查询的结果在第一次请求时存储在索引中,并且存储的索引用于以后的所有查找。对数据库的更改将在进一步请求时添加到索引中。

CouchDB的API完全基于HTTP,因此对数据库的所有请求都是在不同url上的HTTP动词(GET、POST、PUT、DELETE)。我将列出MapReduce查询(用JavaScript编写)以及用于从索引请求相关结果的URL。

1。通过id和作者获取文章

最简单的方法是两个直接的文档查找:

<>之前得到/db/{article_id}GET/db/{author_id} 之前

…其中{author_id}是从文章的author_id字段获得的值。

2。获取指定作者的所有文章

MapReduce

function (doc) {
  if (doc.type === 'article') {
    emit(doc.author_id, doc);
  }
}
<>之前得到/db/_design/cms/_view/articles_by_author吗?关键= " {author_id} "

…其中{author_id}为作者的实际ID。

3。查找作者按创建日期排序的前10篇文章

MapReduce

function (doc) {
  function arrayDateFromTimeStamp(ts) {
    var d = new Date(ts);
    return [d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()];
  }
  var newdoc = doc;
  newdoc._id = doc.author_id;
  newdoc.created_at = arrayDateFromTimeStamp(doc.created_at);
  if (doc.type === 'article') {
    emit(newdoc.created_at, newdoc); 
  }
}

在视图请求中使用?include_docs=true可以在CouchDB中包含样式"join"。如果您在emit的值端包含"_id"键(第二个参数),那么将include_docs=true添加到您的查询参数中将包括指定"_id"引用的文档。在上面的情况下,我们将文档自己的"_id"(我们不再需要)替换为引用的作者的"_id"(文章文档中的"author_id"的值)。请求包含相关作者信息的前10篇文章如下所示:

<>之前得到/db/_design/cms/_view/articles_by_date吗?下行= true&limit = 10 &include_docs = true 之前

请求URL将返回最近的10篇文章的列表,格式类似于:

{"行":[{" id ":"article_id","键":[2011,9,3,12,5,41],"价值":{" _id ":"author_id","标题":"…"},"doc":{"_id":"author_id", "name":"作者名"}}]} 之前

使用相同的索引,您可以获得任何年,月,日,小时等粒度的所有文档的列表,无论是否包含作者数据。

还有一些方法可以使用视图排序将多个文档从单个文档中聚合在一起(例如CMS中的页面引用不同的内容)。我在七月份为CouchConf做的幻灯片中有一些关于如何做到这一点的信息:http://www.slideshare.net/Couchbase/couchconfsfdesigningcouchbasedocuments

如果您还有什么问题,请告诉我。

按id和作者获取文章

:

  • 1查询
  • 2个索引查找
  • 2个数据查找
  • 返回数据=文章+作者

MongoDB :

    <
  • 2查询/gh>
  • 2个索引查找
  • 2个数据查找
  • 返回数据=文章+作者

获取指定作者的所有文章

:

  • 1查询
  • 1索引查找
  • N数据查找
  • 返回的数据= N篇

MongoDB :

  • 1查询
  • 1索引查找
  • N数据查找
  • 返回的数据= N篇

查找作者按创建日期排序的前10篇文章

:

  • 1查询
  • 2个索引查找
  • 11到20个数据查找(文章然后是唯一的作者)
  • 返回的数据= 10篇文章+ 10位作者

MongoDB :

  • 2查询(articles.find().sort().limit(10), authors.find({$in:[article_authors]})
  • )
  • 2个索引查找
  • 11到20个数据查找(文章然后是唯一的作者)
  • 返回的数据= 10篇文章+ 1至10位作者

在两种情况下,MongoDB需要一个额外的查询,但在底层完成大部分相同的总工作。在某些情况下,MongoDB通过网络返回的数据较少(没有重复条目)。连接查询往往受限于所有要连接的数据位于同一框上的要求。如果作者和文章位于不同的地方,那么无论如何你最终都要执行两个查询。

MongoDB倾向于获得更好的"原始"性能,因为它不会在每次写时刷新磁盘(所以它实际上是一个"持久性"权衡)。它还有一个更小的查询解析器,因此每个查询的活动更少。

从基本性能的角度来看,这些东西非常相似。它们只是对你的数据和你想要做的权衡做出不同的假设。

对于MongoDB,您不会为作者记录使用嵌入式文档。因此,预连接是出去的,它是多次访问数据库。但是,您可以缓存作者,并且只需要对每个记录进行第二次访问一次。您指示的查询在MongoDB中非常微不足道。

var article = db.articles.find({id: article_id}).limit(1);
var author = db.authors.find({id: article.author_id});

如果您使用ORM/ODM来管理应用程序中的实体,这将是透明的。不过要去趟db。它们应该是快速反应,两次命中不应该是明显的。

查找给定作者的文章正好相反…

var author = db.authors.find({id: author_name}).limit(1);
var articles = db.articles.find({author_id: author.id});

所以,再次,两个查询,但单个作者获取应该是快速的,可以很容易地缓存。

var articles = db.articles.find({}).sort({created_at: 1}).limit(10);
var author_ids = articles.map(function(a) { return a.author_id });
var authors = db.authors.find({id: { '$in': authors_ids }});

最后,同样是两个查询,但是稍微复杂一点。您可以在mongo shell中运行这些程序,看看结果可能是什么样的。

我不确定这是否值得写一个map reduce来完成。几个快速的往返可能会有更多的延迟,但mongo协议非常快。我不会太担心的。

最后,这样做的实际性能影响…因为理想情况下您只查询文档中的索引字段,所以应该非常快。唯一额外的步骤是获得其他文档的第二次往返,这取决于应用程序和数据库的结构,这可能根本不是什么大问题。您可以告诉mongo只配置超过给定阈值(打开时默认值为100或200ms)的查询,因此您可以密切关注随着数据增长程序所花费的时间。

您在这里拥有的RDMS没有提供的好处是更容易分解数据。当您将应用程序扩展到CMS之外以支持其他功能,但使用相同的身份验证存储时,会发生什么情况?它现在恰好是一个完全独立的数据库,在许多应用程序之间共享。跨数据库执行这些查询要简单得多——对于RDMS存储,这是一个复杂的过程。

我希望这对你的NoSQL发现有帮助!

最新更新