这可能听起来像一个显而易见的问题,但我是CouchDB的新手,所以我认为值得一提的是,CouchDB的结构会改变我不了解的情况。由于我无法控制的原因,我必须从CouchDB构建一个类似队列的结构。为简单起见,假设我正在为稍后执行的作业排队ID。请注意,不会有重复。
我正在试图找出构建这个的最佳方法。正如我目前所看到的,我有几个选择:
queue
数据库中,ID为_id
,并将出列项存储在类似dequeued
数据库中,其ID为{{1} }。除了(强制性)_id
和_id
之外,每个数据库中的每条记录都没有任何其他信息。_rev
条记录和一条_id = 'queue'
条记录。在两个记录中的每个记录中,将存在任意数量的键,每个键将是要执行的作业(或已经执行的作业)的ID。数据库中与键关联的值将无关紧要,可能只是一个布尔值。_id = 'dequeued'
的记录。在该记录中,有两个键:queue
和queue
。这些密钥中的每一个都将具有任意长度的作业执行ID列表作为其关联值。1 稍微不太理想,因为它需要两个数据库,并且 2 让我感觉不好,因为它需要按顺序加载整个排队或出列项目列表阅读列表项或进行任何更改。但是, 3 很好,因为它允许整个ID列表是有序列表而不是键/值对,这使得从列表中选择随机项更容易成为下一个要执行的工作,因为我实际上不需要知道任何关键名称(因为没有)。
我正在寻找能提供最佳性能的产品。有什么想法吗?
对于将来阅读此问题的人,我已经构建了我的CouchDB排队模块dequeued
,正在进行中。
你可以得到它CouchQueue
。
在Github上查看(并请评论,提取请求等)here。
答案 0 :(得分:1)
我建议为每个队列条目使用单独的文档,这样可以避免冲突。
如果您只需要一个带有push()
,pop()
,top()
的队列来添加插入元素并获取一个元素,那么解决方案可能非常简单(如果您希望列表与next()
,或访问第n个元素,它会获得more complicated)。对于具有线性顺序的调度算法(如FIFO,FILO),您可以在插入新文档时实现push()
:
{ type: "queue", inserted: CURRENT_TIME, ... }
top()
作为地图:
function (doc) {
if (doc.type == "queue" && doc.inserted) {
emit(doc.inserted, doc);
}
}
并作为聚合减少(例如,FILO的最大值,FIFO的最小值)。
对于pop()
,您可以查询top()
,然后删除该文档。
Map / reduce必须是确定性的,因此如果你想选择随机元素,你可以使reduce依赖于伪随机(由服务器选择)_id
。
我期待两个问题:
注意并发性:两个进程可以使用top()
请求同一文档,首先将文档作为pop()
的一部分删除,然后第二个将尝试获取已删除的文档。
CouchDB从未真正删除过该文档,只标记为已删除。每个push()
/ pop()
的添加和删除都会增加数据库。您将不得不以某种方式重用文档。也许您对任务进行了一些轮询,这些任务在队列中被插入和删除或重新排序。然后,您可以将queued: true
添加到任务文档,而不是使用type: "queue"
添加单独的文档。
答案 1 :(得分:1)
在队列中为每个元素使用一个文档,并保留一个队列数据库。
我建议使用字段来排序元素,例如.created_at
,其中包含ISO 8601格式的时间戳。
您可以使用.visible
标记切换元素的可见性。
我推荐一个map / reduce视图,类似这样的
function(doc) {
if(doc.visible)
emit(doc.created_at, doc)
}
现在,您可以查询此视图,可以是最早的,也可以是最新的(?descending=true
)。您可以通过更新元素来标记元素,并设置visible = false
。
我编写了一个CouchDB队列,CQS与Amazon SQS API相同。它类似于我描述的内容,除了有一个签出状态消息可以在队列中看不到超时时间。我已经在生产中使用了CQS大约两年,有数亿次更新。