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

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

性能和资源利用率

本部分提供了 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。如果集合中有很多文档,集合扫描将非常慢,因此整体查询性能非常低。因此,explain 命令输出中的 "COLLSCAN" 的存在表明,可以通过创建合适的索引来提高查询性能。

在本例中,查询检查所有文档中的字段 "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 用户)。

使用mongoshell,您可以使用以下查询列出 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— 内部垃圾收集器线程。系统中最多可以同时运行三个垃圾收集器线程。

  • 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: {} });

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

如果查询和内部系统任务的总数超过默认批处理游标大小,则mongoshell 会自动生成一个迭代器对象'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 内核数。有关更多信息,请参阅 实例实例类规格

  • 如果所有群集实例都表现出较高的 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: {} });