性能和资源利用率 - Amazon DocumentDB
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 AWS 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

性能和资源利用率

本部分提供了 Amazon DocumentDB 部署中的常见诊断问题和解决方案。提供的示例使用 mongo shell,并且范围限定为单个实例。要查找实例终端节点,请参阅了解Amazon DocumentDB终端节点.

如何查找并终止长时间运行或受阻的查询?

用户查询可能因查询计划不够理想而运行缓慢,或者由于资源争用而受阻。

要查找因查询计划不够理想而速度缓慢的长时间运行的查询,或者由于资源争用而受阻的查询,请使用 currentOp 命令。可以筛选该命令以帮助缩小要终止的相关查询的列表。长时间运行的查询必须拥有关联的 opid,才能够终止查询。

以下查询使用 currentOp 命令列出受阻或运行时间超过 10 秒的所有查询。

db.adminCommand({ aggregate: 1, pipeline: [ {$currentOp: {}}, {$match: {$or: [ {secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}, {$project: {_id:0, opid: 1, secs_running: 1}}], cursor: {} });

接下来,您可以缩小查询,以查找运行时间超过 10 秒的查询的 opid 并终止它。

查找并终止运行时间超过 10 秒的查询

  1. 查找查询的 opid

    db.adminCommand({ aggregate: 1, pipeline: [ {$currentOp: {}}, {$match: {$or: [{secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}], cursor: {} });

    此操作的输出将类似于下文(JSON 格式)。

    { "waitedMS" : NumberLong(0), "cursor" : { "firstBatch" : [ { "opid" : 24646, "secs_running" : 12 } ], "id" : NumberLong(0), "ns" : "admin.$cmd" }, "ok" : 1 }
  2. 使用 killOp 操作终止查询。

    db.adminCommand({killOp: 1, op: 24646});

如何查看查询计划和优化查询?

如果查询运行缓慢,可能是因为查询执行需要对集合进行完全扫描以选择相关的文档。有时,可通过创建合适的索引提高查询的运行速度。要检测此方案并确定要在其上创建索引的字段,请使用 explain 命令。

注意

Amazon DocumentDB 在利用分布式、容错、自我修复的存储系统的专用数据库引擎上模拟 MongoDB 3.6 API。因此,查询计划和 的输出在 explain() 和 之间Amazon DocumentDB可能不同MongoDB。 希望控制其查询计划的客户可以使用 $hint 运算符强制选择首选索引。

explain 命令下运行要改进的查询,如下所示。

db.runCommand({explain: {<query document>}})

以下是操作示例。

db.runCommand({explain:{ aggregate: "sample-document", pipeline: [{$match: {x: {$eq: 1}}}], cursor: {batchSize: 1}} });

此操作的输出将类似于下文(JSON 格式)。

{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db.test", "winningPlan" : { "stage" : "COLLSCAN" } }, "serverInfo" : { "host" : "...", "port" : ..., "version" : "..." }, "ok" : 1 }

上述输出表明,$match 阶段要求扫描整个集合并检查每个文档中的字段 "x" 是否等于 1。如果集合中有很多文档,集合扫描将非常慢,因此整体查询性能非常低。因此,"COLLSCAN" 命令输出中的 explain 的存在表明,可以通过创建合适的索引来提高查询性能。

在本例中,查询检查所有文档中的字段 "x" 是否等于 1。因此,在字段 "x" 上创建索引,可使查询避免对集合进行完全扫描,并可使用索引更快地返回相关文档。

在字段 "x" 上创建索引后,explain 输出如下所示。

{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db.test", "winningPlan" : { "stage" : "IXSCAN", "indexName" : "x_1", "direction" : "forward" } }, "serverInfo" : { "host" : "...", "port" : ..., "version" : "..." }, "ok" : 1 }

因此,在 "x" 字段上创建索引后,$match 阶段即可使用索引扫描来减少必须对其评估 "x = 1" 谓词的文档的数量。

对于较小的集合,如果性能增益微乎其微,Amazon DocumentDB 查询处理器可以选择不使用索引。

如何列出实例上正在运行的所有操作?

作为用户或主用户,您经常需要列出实例上当前正在运行的所有操作,以进行诊断和故障排除。(有关管理用户的信息,请参阅管理 Amazon DocumentDB 用户.)

借助 mongo shell,您可以使用以下查询列出 Amazon DocumentDB 实例上正在运行的所有操作。

db.adminCommand({currentOp: 1, $all: 1});

该查询返回当前在实例上运行的所有用户查询和内部系统任务的完整列表。

此操作的输出将类似于下文(JSON 格式)。

{ "inprog" : [ { "desc" : "INTERNAL" }, { "desc" : "TTLMonitor", "active" : false }, { "desc" : "GARBAGE_COLLECTION" }, { "client" : ..., "desc" : "Conn", "active" : true, "killPending" : false, "opid" : 195, "ns" : "admin.$cmd", "command" : { "currentOp" : 1, "$all" : 1 }, "op" : "command", "$db" : "admin", "secs_running" : 0, "microsecs_running" : NumberLong(68), "clientMetaData" : { "application" : { "name" : "MongoDB Shell" }, "driver" : { ... }, "os" : { ... } } }], "ok" : 1 }

以下是 "desc" 字段的有效值:

  • INTERNAL — 内部系统任务,如游标清理或过时用户清理任务。

  • TTLMonitor — 生存时间 (TTL) 监控线程。其运行状态在 "active" 字段中反映。

  • GARBAGE_COLLECTION — 内部垃圾收集器线程。系统中最多可以同时运行 3 个垃圾收集器线程。

  • CONN — 用户查询。

  • CURSOR — 操作是一个空闲游标,等待用户调用“getMore”命令以获取下一批结果。在此状态下,游标占用内存,但不消耗任何计算。

上述输出还列出了在系统中运行的所有用户查询。每个用户查询都在数据库和集合的上下文中运行,而这两者的并集称为命名空间。每个用户查询的命名空间都可在 "ns" 字段中获得。

有时,您需要列出在特定命名空间中运行的所有用户查询。因此,必须根据 "ns" 字段筛选先前的输出。以下是实现要筛选的输出的示例查询。此查询列出了当前在数据库"db"和集合中运行的所有用户查询"test"(即 "db.test" 命名空间)。

db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {allUsers: true, idleConnections: true}}, {$match: {ns: {$eq: "db.test"}}}], cursor: {} });

作为系统主用户,您可以查看所有用户的查询以及所有内部系统任务。所有其他用户只能查看其各自的查询。

如果查询和内部系统任务的总数超过默认批处理游标大小mongo,则 shell 会自动生成迭代器对象'it'以查看其余结果。继续执行'it'命令,直到所有结果都用尽。

如何知道查询何时取得进展?

用户查询可能因查询计划不够理想而运行缓慢,或者由于资源争用而受阻。调试此类查询是一个多步骤过程,可能需要多次执行相同的步骤。

调试的第一步是列出长时间运行或受阻的所有查询。以下查询列出了运行时间超过 10 秒或正在等待资源的所有用户查询。

db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {}}, {$match: {$or: [{secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}, {$project: {_id:0, opid: 1, secs_running: 1, WaitState: 1, blockedOn: 1, command: 1}}], cursor: {} });

定期重复上述查询以确定查询列表是否更改以及确定长时间运行的或受阻的查询。

如果相关查询的输出文档具有 WaitState 字段,则表示资源争用是查询运行缓慢或受阻的原因。资源争用可能是由于 I/O、内部系统任务或其他用户查询导致的。

此操作的输出将类似于下文(JSON 格式)。

{ "waitedMS" : NumberLong(0), "cursor" : { "firstBatch" : [ { "opid" : 201, "command" : { "aggregate" : ... }, "secs_running" : 208, "WaitState" : "IO" } ], "id" : NumberLong(0), "ns" : "admin.$cmd" }, "ok" : 1 }

如果跨不同集合的多个查询在同一实例上并发运行,或者实例大小对于运行查询的数据集而言过小,则 I/O 可能会成为瓶颈。如果查询是只读查询,您可以通过在单独的副本中分隔每个集合的查询来缓解以前的情况。对于跨不同集合的并发更新,或者当实例大小对于数据集而言过小时,您可以通过扩展实例来缓解。

如果资源争用是由其他用户查询引起的,则输出文档中的 "blockedOn" 字段将具有影响此查询"opid"的查询的 。使用 "opid" 会跟踪所有查询的 "WaitState""blockedOn" 字段链,以在链头查找查询。

如果链头的任务是内部任务,则在这种情况下,唯一的缓解措施是终止查询并在以后重新运行查询。

下面是一个示例输出,其中,查找查询在由另一个任务拥有的集合锁上被阻止。

{ "inprog" : [ { "client" : "...", "desc" : "Conn", "active" : true, "killPending" : false, "opid" : 75, "ns" : "...", "command" : { "find" : "...", "filter" : { } }, "op" : "query", "$db" : "test", "secs_running" : 9, "microsecs_running" : NumberLong(9449440), "threadId" : 24773, "clientMetaData" : { "application" : { "name" : "MongoDB Shell" }, "driver" : { ... }, "os" : { ... } }, "WaitState" : "CollectionLock", "blockedOn" : "INTERNAL" }, { "desc" : "INTERNAL" }, { "client" : "...", ... "command" : { "currentOp" : 1 }, ... } ], "ok" : 1 }

如果 "WaitState" 具有值 "Latch""SystemLock""BufferLock""BackgroundActivity""Other",则资源争用的来源是内部系统任务。如果这种情况持续了很长时间,则唯一的缓解措施是终止查询并在以后重新运行查询。

如何确定系统突然运行缓慢的原因?

以下是系统减速的一些常见原因:

  • 并发查询之间的过多资源争用

  • 随时间增加的活动并发查询数

  • 内部系统任务,例如 "GARBAGE_COLLECTION"

要监控系统的长期使用情况,请定期运行以下 "currentOp" 查询并将结果输出到外部存储。此查询会计算系统中每个命名空间中的查询和操作数。然后,您可以分析系统使用结果以了解系统上的负载并采取适当措施。

db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {allUsers: true, idleConnections: true}}, {$group: {_id: {desc: "$desc", ns: "$ns", WaitState: "$WaitState"}, count: {$sum: 1}}}], cursor: {} });

此查询返回在每个命名空间中运行的所有查询的聚合、所有内部系统任务以及每个命名空间中唯一的等待状态数(如果有)。

此操作的输出将类似于下文(JSON 格式)。

{ "waitedMS" : NumberLong(0), "cursor" : { "firstBatch" : [ { "_id" : { "desc" : "Conn", "ns" : "db.test", "WaitState" : "CollectionLock" }, "count" : 2 }, { "_id" : { "desc" : "Conn", "ns" : "admin.$cmd" }, "count" : 1 }, { "_id" : { "desc" : "TTLMonitor" }, "count" : 1 } ], "id" : NumberLong(0), "ns" : "admin.$cmd" }, "ok" : 1 }

在前面的输出中,在集合锁定时"db.test"阻止了命名空间中的两个用户查询:命名空间 中的一个查询"admin.$cmd"和一个内部"TTLMonitor"任务。

如果输出表明有许多查询处于受阻等待状态,请参阅如何查找并终止长时间运行或受阻的查询?

如何确定一个或多个集群实例上 CPU 利用率高的原因?

以下部分可帮助您确定实例 CPU 利用率高的原因。您的结果可能因工作负载而异。

根据高实例 CPU 利用率的原因,执行以下一项或多项操作会很有帮助。

  • 如果主实例的 CPU 利用率较高,但副本实例没有显示,请考虑通过客户端读取首选项设置(例如,)跨副本分配读取流量secondaryPreferred。有关更多信息,请参阅连接至 Amazon DocumentDB 作为副本集.

    使用副本进行读取可以允许主实例处理更多写入流量,从而更好地利用集群的资源。从副本进行读取具有最终一致性。

  • 如果 CPU 使用率高是写入工作负载导致的,则将集群实例的大小更改为更大的实例类型将增加可用于处理工作负载的 CPU 核心的数量。有关更多信息,请参阅Instances实例类规格

  • 如果所有集群实例都显示较高的 CPU 利用率,并且工作负载使用副本进行读取,则向集群添加更多副本将增加可用于读取流量的资源。有关更多信息,请参阅将 Amazon DocumentDB 实例添加到集群.

如何确定实例上的打开游标?

当连接到 实例时Amazon DocumentDB,您可以使用 命令db.runCommand("listCursors")列出该实例上打开的游标。给定Amazon DocumentDB实例上任何给定时间最多打开 4,560 个活动游标的限制,具体取决于实例类型。通常建议关闭不再使用的游标,因为游标占用实例上的资源并具有上限。有关特定限制Amazon DocumentDB 配额和限制,请参阅。

db.runCommand("listCursors")

如何确定当前Amazon DocumentDB引擎版本?

要确定您当前的Amazon DocumentDB引擎版本,请运行以下命令。

db.runCommand({getEngineVersion: 1})

此操作的输出将类似于下文(JSON 格式)。

{ "engineVersion" : "2.x.x", "ok" : 1 }
注意

Amazon DocumentDB 3.6 的引擎版本为 1.x.x, Amazon DocumentDB 4.0 的引擎版本为 2.x.x。

如何识别未使用的索引?

最佳实践是定期确定和删除未使用的索引以提高性能和降低成本,因为它消除了不必要的计算、存储,并IOs用于维护索引。要标识给定集合的索引,请运行以下命令:

db.collection.getIndexes()

要确定索引是否已使用,请运行以下命令。命令的输出描述以下内容:

db.collection.aggregate([{$indexStats:{}}]).pretty()
  • ops —使用索引的操作数。如果您的工作负载已运行了足够长时间,并且您确信您的工作负载处于稳定状态,则零ops值表示根本未使用索引。

  • since —自 Amazon DocumentDB 开始收集有关索引使用情况的统计数据以来的时间,这通常是自上次数据库重新启动或维护操作以来的值。

要确定集合的整体索引大小,请运行以下命令:

db.collection.stats()

要删除未使用的索引,请运行以下命令:

db.collection.dropIndex("indexName")

如何识别缺失的索引?

您可以使用Amazon DocumentDB分析器记录慢速查询。慢速查询日志中重复出现的查询可能表示需要一个额外的索引来提高该查询的性能。

您可以通过查找具有一个或多个执行至少一个COLLSCAN阶段的长时间运行的查询来确定有用索引的机会,这意味着查询阶段必须读取集合中的每个文档以便向查询提供响应。

以下示例显示了对在一个大型集合上运行的一系列出租车的查询。

db.rides.count({"fare.totalAmount":{$gt:10.0}}))

要执行此示例,查询必须执行集合扫描(即读取集合中的每个文档),因为 fare.totalAmount 字段上没有索引。此查询的分析Amazon DocumentDB器的输出类似于下文:

{ ... "cursorExhausted": true, "nreturned": 0, "responseLength": 0, "protocol": "op_query", "millis": 300679, "planSummary": "COLLSCAN", "execStats": { "stage": "COLLSCAN", "nReturned": "0", "executionTimeMillisEstimate": "300678.042" }, "client": "172.31.5.63:53878", "appName": "MongoDB Shell", "user": "example" }

要加快此示例中的查询速度,您需要在 上创建索引fare.totalAmount,如下所示。

db.rides.createIndex( {"fare.totalAmount": 1}, {background: true} )
注意

在前台创建的索引(这意味着,如果在创建索引时未提供 {background:true} 选项)采用独占写入锁定,这会阻止应用程序将数据写入到集合中,直到索引构建完成。请注意在生产集群上创建索引时的这一潜在影响。创建索引时,我们建议设置 {background:true}

通常,您希望在具有高基数(例如,大量唯一值)的字段上创建索引。在基数低的字段上创建索引可能会导致未使用的大型索引。Amazon DocumentDB 查询优化程序在创建查询计划时考虑索引的集合的总体大小和选择性。有时,即使存在索引,查询处理器COLLSCAN也会选择 。当查询处理器估计利用索引不会相对于扫描整个集合产生性能优势时,就会发生这种情况。如果要强制查询处理器使用特定索引,可以使用 hint() 运算符,如下所示。

db.collection.find().hint("indexName")

有用查询的摘要

以下查询可用于监控 中的性能和资源利用率Amazon DocumentDB。

  • 使用以下查询列出所有活动。

    db.adminCommand({currentOp: 1, $all: 1});
  • 以下代码列出了所有长时间运行或受阻的查询。

    db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {}}, {$match: {$or: [{secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}, {$project: {_id:0, opid: 1, secs_running: 1, WaitState: 1, blockedOn: 1, command: 1}}], cursor: {} });
  • 以下代码终止查询。

    db.adminCommand({killOp: 1, op: <opid of running or blocked query>});
  • 使用以下代码获取系统状态的聚合视图。

    db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {allUsers: true, idleConnections: true}}, {$group: {_id: {desc: "$desc", ns: "$ns", WaitState: "$WaitState"}, count: {$sum: 1}}}], cursor: {} });