使用Cloudant/CouchDB链式map reduce对聚合进行过滤和排序



我想过滤一个列表并根据聚合对其进行排序;这在SQL中很容易表达,但我对迭代Map Reduce的最佳方式感到困惑。我特别使用Cloudant的"dbcopy"添加到CouchDB中,但是我认为这种方法可能与其他map/reduce架构类似。

伪代码SQL可能看起来像这样:
SELECT   grouping_field, aggregate(*)
FROM     data
WHERE    #{filter}
GROUP BY grouping_field
ORDER BY aggregate(*), grouping_field
LIMIT    page_size

过滤器可能正在寻找匹配项,也可能在一个范围内搜索;例如field in ('foo', 'bar')field between 37 and 42

作为一个具体的例子,考虑一个电子邮件数据集;分组字段可以是"List-id"、"Sender"或"Subject";聚合函数可能为count(*)max(date)min(date);筛选子句可能考虑标志、日期范围或邮箱ID。文档可能看起来像这样:

{
  "id": "foobar", "mailbox": "INBOX", "date": "2013-03-29",
  "sender": "foo@example.com", "subject": "Foo Bar"
}

获取同一发件人的电子邮件计数是微不足道的:

"map": "function (doc) { emit(doc.sender, null) }",
"reduce": "_count"

和Cloudant有一个很好的例子,排序计数的第二次通过的地图减少。但是当我还想过滤时(例如,通过邮箱),事情很快就会变得混乱。

如果我将过滤器添加到视图键(例如,最终结果看起来像{"key": ["INBOX", 1234, "foo@example.com"], "value": null}),那么在单个过滤器值中按计数排序是微不足道的。但是,使用多个过滤器按计数排序数据将需要遍历整个数据集(每个键),这对于大型数据集来说太慢了。

或者我可以为每个潜在的过滤器选择创建一个索引;例如,最终结果看起来像{"key": [["mbox1", "mbox2"], 1234, "foo@example.com"], "value": null},(当同时选择"mbox1"one_answers"mbox2"时)或{"key": [["mbox1"], 1234, "foo@example.com"], "value": {...}},(当只选择"mbox1"时)。这很容易查询,而且很快。但是,索引的磁盘大小似乎会呈指数级增长(随着不同过滤字段的数量的增加)。对于开放式数据(如日期范围)的过滤,它似乎是完全站不住脚的。

最后,我可以动态地生成视图,这些视图只在需要时动态地处理所需的过滤器,并在不再使用它们后将它们拆除(以节省磁盘空间)。缺点是代码复杂度大大增加,而且每次选择新的过滤器时都要付出很大的前期成本。

有更好的方法吗?

我已经考虑了将近一天了,我认为没有比你提议的更好的方法了。你们面临的挑战如下:

1)聚合工作(count, sum等)只能在CouchDB/Cloudant API中通过物化视图引擎(mapreduce)完成。

2)虽然group_level API在查询时提供了一定的灵活性来指定可变粒度,但对于任意布尔查询来说,它不够灵活。

3)通过基于lucene的_search API,在Cloudant API中可以实现任意布尔查询。但是,_search API不支持聚合post查询。对你想要做的事情的有限支持只能在lucene中使用faceting,这在Cloudant中还不支持。即使这样,我相信它可能只支持count而不支持sum或更复杂的聚合。

我认为最好的选择是使用_search API并使用sort、group_by或group_sort,然后在客户端进行聚合。要测试的一些示例url如下所示:

GET/db/_design/ddoc _search/indexname吗?q =名字:迈克和年龄:[1.2 - 4.5],那种=("年龄"、"名称")

GET/db/_design/ddoc _search/indexname吗?q=name:mike AND group_by="mailbox" AND group_sort=["age","name"]

相关内容

  • 没有找到相关文章

最新更新