CouchDB:根据时间戳返回最新类型的文档

Posted

技术标签:

【中文标题】CouchDB:根据时间戳返回最新类型的文档【英文标题】:CouchDB: Return Newest Documents of Type Based on Timestamp 【发布时间】:2012-04-30 19:04:53 【问题描述】:

我有一个系统接受来自各种不同来源的状态更新,每个状态更新都会创建一个具有以下结构的新文档:


 "type": "status_update",
 "source_id": "truck1231",
 "timestamp": 13023123123,
 "location": "Boise, ID"

数据纯粹是示例,但可以理解。

现在,这些文档每隔一小时左右生成一次。一小时后,我们可能会插入:


 "type": "status_update",
 "source_id": "truck1231",
 "timestamp": 13023126723,
 "location": "Madison, WI"

我感兴趣的只是查看来自每个独特来源的最新更新。我目前正在通过以下地图来做到这一点:

function(doc) 
  if (doc.type == "status_update") 
    emit(doc.source_id, doc);
  

还有一个减少:

function(keys, values, rereduce) 
  var winner = values[0];
  var i = values.length;
  while (i--) 
    var val = values[i];
    if (val.timestamp > winner.timestamp) winner = val;
  
  return winner;

使用group=true 查询数据作为reduce。这可以按预期工作,并提供仅包含最新更新的键控结果。

问题是它非常慢,需要我在 CouchDB 配置中reduce_limit=false

感觉必须有一种更有效的方法来做到这一点。更新同一个文档不是一种选择——即使在这种情况下我不需要它,历史也很重要。在客户端处理数据也不是一种选择,因为这是一个 CouchApp,系统中的文档数量实际上非常大,通过网络发送它们并不实际。

提前致谢。

【问题讨论】:

如何更新文档本身,并将“旧”版本作为附件添加到相关文档中? (并为每个新的状态更改重复该过程) 哇,卡车 1231 每小时开 1,700 英里!还不错。 我在那里等待一个机智的评论;) 【参考方案1】:

您可以使用_stats built-in reduce function 获取每个源的最新时间戳,然后执行另一个查询以获取文档。以下是观点:

"views": 
  "latest_update": 
    "map": "function(doc)  if (doc.type == 'status_update') emit(doc.source_id, doc.timestamp); ",
    "reduce": "_stats"
  ,
  "status_update": 
    "map": "function(doc)  if (doc.type == 'status_update') emit([doc.source_id, doc.timestamp], 1); "
  

首先使用group=true 查询latest_update,然后使用类似(正确的url 编码)查询status_update

keys=[["truck123",TS123],["truck234",TS234],...]&include_docs=true

其中 TS123 和 TS234 是 latest_update 返回的 max 的值。

【讨论】:

+1 是的,我忘了 _stats。我相信您的第一个观点,latest_update 回答了 OP 的问题,即获得相同的结果但性能更好。 我写了stew来解决这个问题;但是由于一个 CouchDB 错误(reduce 函数不支持 require),它还不能工作。 github.com/iriscouch/stew 这是完美的——我不知道 _stats 函数,它似乎工作得很好。谢谢! 一个_max 内置函数在这里会很有帮助,以尽量减少数据传输......【参考方案2】:

CouchDB map/reduce 增量这基本上意味着结果总是被缓存,因此对相同视图的后续请求(即使具有不同的搜索参数)“免费”运行(或以对数时间) .

但是,严格来说,reduce 组并非如此。有时必须在运行中重新减少部分结果。也许这就是你要打的。

相反,一个地图视图(即没有 reduce 函数)如何发出这样的行,以数组作为键:

// Row diagram (pseudo-code, just to show the concept).
// Key                    , Value
// [source_id, timestamp] , null // value is not very important in this example
["truck1231", 13023123123], null
["truck1231", 13023126723], null
["truck5555", 13023126123], null
["truck6666", 13023000000], null

注意源的所有时间戳如何“聚集”在一起。 (实际上,他们是collate。)要找到"truck1231" 的最新时间戳,只需请求该“丛”中的最后一行。为此,请使用 limit=1 参数从末尾进行降序查询。要指定“结束”,请使用“高键”值作为键中的第二个元素(有关详细信息,请参见排序规则链接)。

?descending=true&limit=1&startkey=["truck1231",]

(实际上,由于您的时间戳是整数,您可以发出它们的否定,例如-13023123123。这将简化您的查询,但我不知道,这对我来说似乎是在玩火。)

为了生成这些类型的行,我们使用如下的 map 函数:

function(doc) 
  // Emit rows sorted first by source id, and second by timestamp
  if (doc.type == "status_update" && doc.timestamp) 
    emit([doc.source_id, doc.timestamp], null) // Using `doc` as the value would be fine too
  

【讨论】:

重新阅读您的问题后,我突然想到您可能想要一个“报告”类型的结果,以便在一个大结果中为您提供 所有 来源的最新更新。如果是这样,请告诉我。我认为您的 reduce 功能仍有改进的空间。它不应该需要禁用 reduce_limit 所以也许我们可以调试一下而不是这个答案。 就是这种情况——对于我目前拥有的解决方案,这意味着我只有一个查询要进行,但我可以处理 _stats 解决方案中的两个查询,如果性能更高,我可以处理其他查询。 【参考方案3】:

我怀疑它之所以慢只是因为您发出了整个文档,这意味着需要存储和移动大量数据来计算您的最终值。尝试发出时间戳:

function(doc) 
  if (doc.type == "status_update") 
    emit(doc.source_id, [doc._id,doc.timestamp]);
  


function(keys, values, rereduce) 
  var winner = values[0];
  var i = values.length;
  while (i--) 
    var val = values[i];
    if (val[1] > winner[1]) winner = val;
  
  return winner;

这应该为每个键提供一个[id,timestamp] 对,而不会太慢或不必在视图中存储太多数据。

在客户端获得标识符列表后,使用批量 GET API 发送第二个请求:

_all_docs?keys=[id1,id2,id3,...,idn]&include_docs=true 

这将在一个请求中获取所有文档。

【讨论】:

以上是关于CouchDB:根据时间戳返回最新类型的文档的主要内容,如果未能解决你的问题,请参考以下文章

couchdb 按值、限制和按时间顺序获取

如何从具有最早和最新时钟时间的时间戳索引数组中返回时间戳?

如何仅从组中查询具有最新时间戳的文档?

如何编写 hql 查询或条件查询以根据时间戳获取最新记录

时间戳如何储存数据

不知道如何在 CouchDB 中创建特定的 MapReduce