MongoDB队列非常慢,用于查找和修改


MongoDB queue very SLOW for findandmodify

我正在使用MongoDB作为队列,PHP-Queue作为获取数据的一种方式。这是一个 POC,我在 OSX 机器上运行。我看到Mongo的性能非常慢,即findmodify函数。我在PHP端做了一堆测试,PHP处理只占了大约5%的时间。当我填充 Mongo 集合时,比如说,10,000 条消息,它填充得非常快,大约需要 3-5 秒。但是当我清空它时,大约需要 250 秒。这个时间只有大约 10 秒在 php 端。检查mongod进程,它永远不会超过60MB,但CPU在整个过程中峰值超过90%。我已经为集合编制了索引,下面是消息数据和索引的示例。

示例消息(这是队列中 10,000 条类似消息之一):

{
  "_id": ObjectId("526c47d5c5008c1d5cd63ef8"),
  "payload": {
    "0": {
      "EVENT_HEADER_KEY": NumberInt(9094775),
      "event_name": "Account Change",
      "source_name": "Work",
      "event_category_name": "Complex Events",
      "EVENT_TIMESTAMP": "Aug 17 2013 12:00:00:000AM",
      "PARENT_HEADER_KEY": null,
      "year": NumberInt(2013),
      "month": NumberInt(10),
      "Company_Name": "ACME PRODUCTS, INC.",
      "Company_Email": "blabla",
      "Company_Phone": "555-555-5555",
      "First_Name": "Jon",
      "Last_Name": "Doe",
      "ID_NUMBER": "111111111",
      "created_by": "Load Job Name",
      "created_at": "Oct 18 2013 04:07:31:140PM",
      "product_analytical_category": "blabla",
      "_Event_Type": "blabla",
      "CUSTOMER_ID": "111111111"
   }
 },
  "running": false,
  "resetTimestamp": ISODate("2038-01-19T03:14:07.0Z"),
  "earliestGet": ISODate("1970-01-01T00:00:00.0Z"),
  "priority": 0,
  "created": ISODate("2013-10-26T22:53:09.440Z")
}   

此集合的索引,似乎是自动创建的:

{
   "_id": NumberInt(1)
}

检查 mongo.log,我可以看到当我清空队列时,每条消息大约 1 毫秒,大约 70 条消息,然后 opid 会发生变化,然后会有 300-900 毫秒的延迟,然后它以每条消息约 1 毫秒的相同速度继续使用新的 opid。这些 opid 更改约占 250 秒处理时间的 50-100 秒,因此还有更多的事情要做。

摘自蒙戈.log:

**Sat Oct 26 15:15:25.189** [conn4] warning: ClientCursor::yield can't unlock b/c of recursive lock ns: test.abe top: { **opid: 20064**, active: true, secs_running: 0, op: "query", ns: "test", query: { findandmodify: "abe", query: { running: false, earliestGet: { $lte: new Date(1382825725143) } }, update: { $set: { resetTimestamp: new Date(1382825785000), running: true } }, fields: { payload: 1 }, sort: { priority: 1, created: 1 } }, client: "127.0.0.1:53045", desc: "conn4", threadId: "0x119024000", connectionId: 4, locks: { ^: "w", ^test: "W" }, waitingForLock: false, numYields: 0, lockStats: { timeLockedMicros: {}, timeAcquiringMicros: { r: 0, w: 3 } } }
**Sat Oct 26 15:15:25.190** [conn4] warning: ClientCursor::yield can't unlock b/c of recursive lock ns: test.abe top: { **opid: 20064**, active: true, secs_running: 0, op: "query", ns: "test", query: { findandmodify: "abe", query: { running: false, earliestGet: { $lte: new Date(1382825725143) } }, update: { $set: { resetTimestamp: new Date(1382825785000), running: true } }, fields: { payload: 1 }, sort: { priority: 1, created: 1 } }, client: "127.0.0.1:53045", desc: "conn4", threadId: "0x119024000", connectionId: 4, locks: { ^: "w", ^test: "W" }, waitingForLock: false, numYields: 0, lockStats: { timeLockedMicros: {}, timeAcquiringMicros: { r: 0, w: 3 } } }
**Sat Oct 26 15:15:25.507** [conn4] warning: ClientCursor::yield can't unlock b/c of recursive lock ns: test.abe top: { **opid: 20141**, active: true, secs_running: 0, op: "query", ns: "test", query: { findandmodify: "abe", query: { running: false, earliestGet: { $lte: new Date(1382825725501) } }, update: { $set: { resetTimestamp: new Date(1382825785000), running: true } }, fields: { payload: 1 }, sort: { priority: 1, created: 1 } }, client: "127.0.0.1:53045", desc: "conn4", threadId: "0x119024000", connectionId: 4, locks: { ^: "w", ^test: "W" }, waitingForLock: false, numYields: 0, lockStats: { timeLockedMicros: {}, timeAcquiringMicros: { r: 0, w: 3 } } }

这在这 10,000 条消息的整个日志中基本相同。将有一个很长的 findandmodify() 序列,每条消息只需要 1 毫秒,然后 opid 发生变化,并且有一个延迟,可能需要几乎一秒钟。我不知道这是否表明有什么重要的事情,但我是 Mongo 的新手,我正在尝试找到任何看起来很有希望的模式。

更新:

查询检查字段"running"是否为假,并且还会检查 earliestGet 字段是否比纪元(即 1-1-1970)更新。我向这些字段添加了索引,但无济于事。由于这些字段对于集合中的所有消息(分别为"false"和 1970 年 1 月 1 日)都是相同的,也许这就是为什么我对它们的索引只会增加查询时间的原因。我不知道我应该怎么做才能让它正常工作。似乎它应该抓取它发现的第一条比 1970 年 1 月 1 日更新的记录,但显然 Mongo 仍然遍历了整个集合,这使得查询太慢而无法实用。此外,即使我没有选择标准,我仍然得到 202 秒的响应时间 - 更快,但仍然无法接受。我还看到那些"yield 无法解锁递归锁 ns:"消息,我认为这些消息只会在查询未索引字段时显示。

您缺少一个非常关键的索引,该索引将用于findAndModify命令的查询和排序部分。 如果没有该索引,则会强制每个命令扫描整个集合,然后对整个结果集进行排序,这是低效的。 现在你说"我已经索引了集合",但你只提到了"_id"索引,它总是存在的,无法帮助你。

建议:在正在运行的字段上添加复合索引,并且至少尽早获取。 让索引包含排序字段可能会有所帮助,但由于我希望每个查询的匹配文档数量相对较小,因此内存中排序可能不是一个因素。

命令:

db.abe.ensureIndex({running:1, earliestGet:1})

在评论讨论中发现,正在运行的 earliestGet 索引根本没有选择性 - 但由于您正在排序以仅获取第一个匹配的文档,因此另一种方法是在排序列上添加索引:

命令:

db.abe.ensureIndex({ priority: 1, created: 1 })

如果没有更详细的描述,你在修改阶段到底在做什么,很难给出明确的答案。从日志来看,您似乎执行了这样的更新:

db.abc.findAndModify(
    query: { running: false, earliestGet: { $lte: new Date(1382825725143) } },
    update: { $set: { resetTimestamp: new Date(1382825785000), running: true } }
)

并且earliestGet字段和running字段没有索引。由于基数低,在running上添加索引应该不会产生真正的影响,但earliestGet上缺乏索引可能是一个真正的问题。

关于warning: ClientCursor::yield can't unlock b/c of recursive lock ns:消息,您可以看到这个问题: MongoDB:在两个流程实例中使用 findAndModify 时,Geting "客户端光标::yield 无法解锁递归锁的 b/c"警告