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

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

性能和资源利用率

本部分提供了 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 核心数量。有关更多信息,请参阅 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: {} });