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

时间:2011-10-01 00:38:02

标签: mongodb couchdb rdbms riak nosql

为了跟进我关于modeling relational data with nosql的问题,我已经阅读了几篇关于这个主题的文章:

Nosql doesn't mean non-relational

Nosql Ecommerce Example

他们似乎建议nosql可以处理规​​范化的关系数据。

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

以下是系统需要支持的操作:

  1. 通过ID和作者
  2. 获取文章
  3. 获取特定作者的所有文章
  4. 查找按创作日期排序的作者的前10篇文章
  5. 如果相同的数据存储在RDBMS上,我想了解这些操作的性能。 特别是,请指定操作是否使用MapReduce,需要多次访问nosql商店(链接),或pre-join

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

    修改1:

    Spring-data project可用于Riak和Mongodb

3 个答案:

答案 0 :(得分:5)

只是想为可能有点好奇的人投入CouchDB答案。 :)

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

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

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

<强> 1。按ID和作者

获取文章

执行此操作的最简单方法是两个直接文档查找:

GET /db/{article_id}
GET /db/{author_id}

...其中{author_id}是从文章的author_id字段中获取的值。

<强> 2。获取特定作者的所有文章

MapReduce的

function (doc) {
  if (doc.type === 'article') {
    emit(doc.author_id, doc);
  }
}
GET /db/_design/cms/_view/articles_by_author?key="{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中包含样式“连接”。如果在emit(第二个参数)的值侧包含“_id”键,则在查询参数中添加include_docs=true将包含指定的“_id”引用的文档。在上面的例子中,我们'用引用的作者的“_id”(文章文档中的“author_id”的值)替换文档自己的“_id”(我们不再需要)。请求前10篇文章及其相关作者信息如下:

GET /db/_design/cms/_view/articles_by_date?descending=true&limit=10&include_docs=true

请求该URL将以类似于:

的格式返回最近10篇文章的列表
{"rows":[
  { "id":"article_id",
    "key":[2011, 9, 3, 12, 5, 41],
    "value":{"_id":"author_id", "title":"..."},
    "doc":{"_id":"author_id", "name":"Author Name"}
  }
]}

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

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

如果您有任何其他问题,请告诉我。

答案 1 :(得分:4)

  

通过id和作者

获取文章

<强> SQL

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

<强> MongoDB的

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

获取特定作者的所有文章

<强> SQL

  • 1个查询
  • 1索引查找
  • N次数据查找
  • 返回的数据= N篇文章

<强> MongoDB的

  • 1个查询
  • 1索引查找
  • N次数据查找
  • 返回的数据= N篇文章
  

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

<强> SQL

  • 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往往会获得更好的“原始”性能,因为它不会在每次写入时刷新到磁盘(因此它实际上是“持久性”权衡)。它还有一个小得多的查询解析器,因此每个查询的活动较少。

从基本的表现角度来看,这些事情非常相似。他们只是对您的数据做出不同的假设,以及您想要做出的权衡。

答案 2 :(得分:2)

对于MongoDB,您不会将嵌入文档用于作者记录。因此,预加入已经结束,它是多次访问数据库。但是,您可以缓存作者,只需要为每条记录进行一次第二次旅行。您指出的查询在MongoDB中非常简单。

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

如果您使用ORM / ODM管理应用程序中的实体,这将是透明的。这将是两次到数据库的旅行。它们应该是快速的反应,两个命中根本不应该是明显的。

查找给定作者的文章只是反过来......

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中运行它们以查看结果可能是什么样的。

我不确定写地图是否值得完成。几次快速往返可能会有更多的延迟,但mongo协议非常快。我不会过分担心它。

最后,这样做的实际性能影响......理想情况下,您只需要查询文档中的索引字段,它应该非常快。唯一的额外步骤是获取其他文档的第二次往返,具体取决于您的应用程序和数据库的结构,这可能不是什么大问题。您可以告诉mongo仅查看超过给定阈值的查询(默认情况下打开时默认为100或200毫秒),这样您就可以随时关注随着数据增长而占用的程序。

你在这里有一个RDMS不提供的装备更容易分解数据。当您将应用程序扩展到CMS之外以支持其他内容但使用相同的身份验证存储时会发生什么?它恰好是一个完全独立的DB,它在许多应用程序中共享。跨dbs执行这些查询要简单得多 - RDMS存储它是一个复杂的过程。

我希望这可以帮助您进行NoSQL发现!