使用调整 Gremlin 查询explain和profile - Amazon Neptune
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 Amazon Web Services 服务入门

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

使用调整 Gremlin 查询explainprofile

您经常可以使用从 Neptune 获得的报告中提供的信息,在 Amazon Neptune 中调整 Gremlin 查询以获得更好的性能说明轮廓API。要做到这一点,有助于了解 Neptune 如何处理 Gremlin 遍历。

重要

在 TinkerPop 3.4.11 版本中进行了一项更改,这提高了查询处理方式的正确性,但目前有时会严重影响查询性能。

例如,这种查询的运行速度可能会慢得多:

g.V().hasLabel('airport'). order(). by(out().count(),desc). limit(10). out()

由于 TinkerPop 3.4.11 的变化,限制步骤之后的顶点现在以非最佳方式获取。为避免这种情况,您可以通过在order().by(). 例如:

g.V().hasLabel('airport'). order(). by(out().count(),desc). limit(10). barrier(). out()

TinkerPop 3.4.11 已在 Neptune 启用引擎版本 1.0.5.0.

了解 Neptune 中的 Gremlin 遍历处理

当 Gremlin 遍历被发送到 Neptune 时,有三个主要流程可以将遍历转换为基础执行计划以供引擎执行。这些都是解析、转换和优化:

3 个流程将 Gremlin 查询转换为执行计划。

遍历解析过程

处理遍历的第一步是将其解析成一种公共语言。在 Neptune 中,这种共同语言是 TinkerPop 步骤的一组,它们是TinkerPop API. 这些步骤中的每个步骤都表示遍历中的一个计算单位。

您可以将 Gremlin 遍历作为字符串或字节码发送给 Neptune。REST 终端节点和 Java 客户端驱动程序submit()方法将遍历作为字符串发送,如下例所示:

client.submit("g.V()")

应用程序和语言驱动程序Gremlin 语言变体 (GLV)以字节码发送遍历。

遍历转换过程

处理遍历的第二步是将 TinkerPop 步骤转换为一组转换后的和未转换的 Neptune 步骤。Apache TinkerPop Gremlin 查询语言中的大多数步骤都转换为 Neptune 特定的步骤,这些步骤经过优化,可在底层 Neptune 引擎上运行。当在遍历中遇到没有等效 Neptune 的 TinkerPop 步骤时,该步骤以及遍历中的所有后续步骤都将由 TinkerPop 查询引擎处理。

有关在何种情况下可以转换的步骤的更多信息,请参阅。Grelin 步骤支持.

遍历优化过程

遍历处理的最后一步是通过优化程序运行一系列转换和未转换的步骤,以尝试确定最佳的执行计划。此优化的结果是 Neptune 引擎处理的执行计划。

使用 Neptune Grelinexplain用于调整查询的 API

Neptune 解释 API 与 Gremlin 不一样explain()步骤。它返回 Neptune 引擎在执行查询时将处理的最终执行计划。由于它不执行任何处理,因此无论使用的参数如何,它都会返回相同的计划,且其输出不包含有关实际执行情况的统计信息。

考虑以下简单的穿越,它可以找到安克雷奇的所有机场顶点:

g.V().has('code','ANC')

通过以下两种方法可以通过 Neptune 运行此遍历。explainAPI。第一种方法是对解释端点进行 REST 调用,如下所示:

curl -X POST https://your-neptune-endpoint:port/gremlin/explain -d '{"gremlin":"g.V().has('code','ANC')"}'

第二种方法是使用 Neptune 工作台%%gremlin使用细胞魔法explain参数。这将细胞体内包含的穿越传递给 NeptuneexplainAPI 然后在运行单元格时显示结果输出:

%%gremlin explain g.V().has('code','ANC')

在任一情况下,结果explainAPI 输出描述了海王星穿越的执行计划。如下图所示,该计划包括处理管道中 3 个步骤中的每个步骤:

为简单的 Gremlin 遍历解释 API 输出。

通过查看未转换的步骤来调整遍历

Neptune 首先要寻找的东西之一explainAPI 输出适用于未转换为 Neptune 原生步骤的 Gremlin 步骤。在查询计划中,遇到无法转换为 Neptune 本机步骤的步骤时,该步骤以及计划中的所有后续步骤都将由 Gremlin 服务器处理。

在上述示例中,遍历中的所有步骤都已转换。我们来检查explain此遍历的 API 输出:

g.V().has('code','ANC').out().choose(hasLabel('airport'), values('code'), constant('Not an airport'))

正如你在下图中看到的那样,Neptune 无法转换choose()步骤:

解释并非所有步骤都可以转换的 API 输出。

您可以执行几项操作来优化遍历的性能。首先是以消除无法转换的步骤的方式重写它。另一个是将步骤移动到遍历的结束,以便所有其他步骤都可以转换为本地步骤。

包含未转换步骤的查询计划并不总是需要调整。如果无法转换的步骤处于遍历的末尾,并且与输出的格式相关,而不是如何遍历图表,那么它们可能对性能没有什么影响。

在检查 Neptune 的输出时要注意的另一件事explainAPI 是不使用索引的步骤。以下穿越查找所有有航班降落在安克雷奇的机场:

g.V().has('code','ANC').in().values('code')

此遍历的解释 API 的输出为:

******************************************************* Neptune Gremlin Explain ******************************************************* Query String ============ g.V().has('code','ANC').in().values('code') Original Traversal ================== [GraphStep(vertex,[]), HasStep([code.eq(ANC)]), VertexStep(IN,vertex), PropertiesStep([code],value)] Converted Traversal =================== Neptune steps: [ NeptuneGraphQueryStep(PropertyValue) { JoinGroupNode { PatternNode[(?1, <~label>, ?2, <~>) . project distinct ?1 .] PatternNode[(?1, <code>, "ANC", ?) . project ask .] PatternNode[(?3, ?5, ?1, ?6) . project ?1,?3 . IsEdgeIdFilter(?6) .] PatternNode[(?3, <~label>, ?4, <~>) . project ask .] PatternNode[(?3, ?7, ?8, <~>) . project ?3,?8 . ContainsFilter(?7 in (<code>)) .] }, annotations={path=[Vertex(?1):GraphStep, Vertex(?3):VertexStep, PropertyValue(?8):PropertiesStep], maxVarId=9} }, NeptuneTraverserConverterStep ] Optimized Traversal =================== Neptune steps: [ NeptuneGraphQueryStep(PropertyValue) { JoinGroupNode { PatternNode[(?1, <code>, "ANC", ?) . project ?1 .], {estimatedCardinality=1} PatternNode[(?3, ?5, ?1, ?6) . project ?1,?3 . IsEdgeIdFilter(?6) .], {estimatedCardinality=INFINITY} PatternNode[(?3, ?7=<code>, ?8, <~>) . project ?3,?8 .], {estimatedCardinality=7564} }, annotations={path=[Vertex(?1):GraphStep, Vertex(?3):VertexStep, PropertyValue(?8):PropertiesStep], maxVarId=9} }, NeptuneTraverserConverterStep ] Predicates ========== # of predicates: 26 WARNING: reverse traversal with no edge label(s) - .in() / .both() may impact query performance

这些区域有:WARNING发生在输出底部的消息是因为in()无法使用 Neptune 维护的 3 个索引中的一个处理遍历中的步骤(请参阅如何将语句编制 Neptune 中的索引Gremlin 在 Neptune 中的声明)。由于in()step 不包含边缘过滤器,无法使用SPOGPOGS要么GPSO索引。相反,Neptune 必须执行联合扫描才能找到请求的顶点,这样的效率要低得多。

在这种情况下,有两种方法可以调整遍历。首先是将一个或多个筛选条件添加到in()步骤,以便可以使用索引查找来解析查询。在上述示例中,可能为:

g.V().has('code','ANC').in('route').values('code')

来自 Neptune 的输出explain修订后遍历的 API 不再包含WARNING消息:

******************************************************* Neptune Gremlin Explain ******************************************************* Query String ============ g.V().has('code','ANC').in('route').values('code') Original Traversal ================== [GraphStep(vertex,[]), HasStep([code.eq(ANC)]), VertexStep(IN,[route],vertex), PropertiesStep([code],value)] Converted Traversal =================== Neptune steps: [ NeptuneGraphQueryStep(PropertyValue) { JoinGroupNode { PatternNode[(?1, <~label>, ?2, <~>) . project distinct ?1 .] PatternNode[(?1, <code>, "ANC", ?) . project ask .] PatternNode[(?3, ?5, ?1, ?6) . project ?1,?3 . IsEdgeIdFilter(?6) . ContainsFilter(?5 in (<route>)) .] PatternNode[(?3, <~label>, ?4, <~>) . project ask .] PatternNode[(?3, ?7, ?8, <~>) . project ?3,?8 . ContainsFilter(?7 in (<code>)) .] }, annotations={path=[Vertex(?1):GraphStep, Vertex(?3):VertexStep, PropertyValue(?8):PropertiesStep], maxVarId=9} }, NeptuneTraverserConverterStep ] Optimized Traversal =================== Neptune steps: [ NeptuneGraphQueryStep(PropertyValue) { JoinGroupNode { PatternNode[(?1, <code>, "ANC", ?) . project ?1 .], {estimatedCardinality=1} PatternNode[(?3, ?5=<route>, ?1, ?6) . project ?1,?3 . IsEdgeIdFilter(?6) .], {estimatedCardinality=32042} PatternNode[(?3, ?7=<code>, ?8, <~>) . project ?3,?8 .], {estimatedCardinality=7564} }, annotations={path=[Vertex(?1):GraphStep, Vertex(?3):VertexStep, PropertyValue(?8):PropertiesStep], maxVarId=9} }, NeptuneTraverserConverterStep ] Predicates ========== # of predicates: 26

如果你正在运行许多此类遍历,另一种选择是在具有可选OSGP已启用索引(请参阅启用 OSGP 索引)。启用OSGP指数有缺点:

  • 必须在数据库集群中启用它,然后才能加载任何数据。

  • 顶点和边缘的插入速率最多可能会降低 23%。

  • 存储使用量将增加约 20%。

  • 将请求分散到所有索引的读取查询可能会增加延迟。

OSGPindex 对于一组受限的查询模式来说很有意义,但除非你频繁运行这些模式,否则通常最好尝试确保可以使用三个主索引来解析你编写的遍历。

使用大量谓词

Neptune 将图表中的每个边缘标签和每个不同的顶点或边缘属性名称视为谓词,默认情况下设计用于处理相对较少的不同谓词。当你的图表数据中有几千个谓词时,性能可能会降低。

Neptuneexplain如果是这种情况,输出会警告你:

Predicates ========== # of predicates: 9549 WARNING: high predicate count (# of distinct property names and edge labels)

如果不方便返工数据模型以减少标签和属性的数量,从而减少谓词的数量,那么调整遍历的最佳方法是在具有OSGP如上所述,已启用索引。

使用 Neptune Grelinprofile用于调整遍历的 API

NeptuneprofileAPI 与 Gremlin 完全不同profile()步骤。类似于explainAPI,其输出包括 Neptune 引擎在执行遍历时使用的查询计划。此外,profile根据遍历参数的设置方式,输出包括遍历的实际执行统计信息。

再次,进行简单的穿越,找到安克雷奇的所有机场顶点:

g.V().has('code','ANC')

explainAPI,您可以调用profile使用 REST 调用的 API:

curl -X POST https://your-neptune-endpoint:port/gremlin/profile -d '{"gremlin":"g.V().has('code','ANC')"}'

你也可以使用 Neptune 工作台%%gremlin使用细胞魔法profile参数。这将细胞体内包含的穿越传递给 NeptuneprofileAPI 然后在运行单元格时显示结果输出:

%%gremlin profile g.V().has('code','ANC')

在任一情况下,结果profile如下图所示,API 输出包含 Neptune 的遍历执行计划和计划执行情况的统计信息:

Neptune 的一个例子profileAPI 输出。

Inprofile输出时,执行计划部分仅包含遍历的最终执行计划,而不包含中间步骤。管道部分包含执行的物理管道操作以及遍历执行所花费的实际时间(以毫秒为单位)。运行时指标对于比较两个不同版本的遍历版本在优化它们时所花费的时间非常有用。

注意

遍历的初始运行时通常比后续运行时长,因为第一个运行时会导致缓存相关数据。

的第三部分profile输出包含执行统计信息和遍历的结果。要了解这些信息在调整穿越过程中如何有用,请考虑以下遍历,它会查找每个名称以 “Anchora” 开头的机场,以及从这些机场两跳到的所有机场、返回机场代码、航班路线和距离:

%%gremlin profile g.withSideEffect("Neptune#fts.endpoint", "{your-OpenSearch-endpoint-URL"). V().has("city", "Neptune#fts Anchora~"). repeat(outE('route').inV().simplePath()).times(2). project('Destination', 'Route'). by('code'). by(path().by('code').by('dist'))

Neptune 中的穿越指标profileAPI 输出

全部可用的第一组指标profile输出是遍历指标。这些类似于 Gremlinprofile()步骤指标,有几个区别:

Traversal Metrics ================= Step Count Traversers Time (ms) % Dur ------------------------------------------------------------------------------------------------------------- NeptuneGraphQueryStep(Vertex) 3856 3856 91.701 9.09 NeptuneTraverserConverterStep 3856 3856 38.787 3.84 ProjectStep([Destination, Route],[value(code), ... 3856 3856 878.786 87.07 PathStep([value(code), value(dist)]) 3856 3856 601.359 >TOTAL - - 1009.274 -

遍历度量表的第一列列列出了遍历执行的步骤。前两个步骤通常是海王星特定的步骤,NeptuneGraphQueryStepNeptuneTraverserConverterStep.

NeptuneGraphQueryStep表示可以由 Neptune 引擎本地转换和执行的整个遍历部分的执行时间。

NeptuneTraverserConverterStep表示将这些转换后的步骤的输出转换为 TinkerPop 遍历器的过程,允许处理无法转换的步骤(如果有)或以 TinkerPOP 兼容的格式返回结果的步骤。

在上面的例子中,我们有几个未转换的步骤,因此我们看到这些 TinkerPop 步骤中的每个步骤(ProjectStepPathStep) 然后在表格中显示为一行。

表中的第二栏,Count,报告的数量代表的通过该步骤的遍历器,而第三列Traversers,报告通过该步骤的遍历者数量,如TinkerPop 个人资料步骤文档.

在我们的例子中,有 3,856 个顶点和 3,856 个遍历者由NeptuneGraphQueryStep,而且在剩余的处理过程中,这些数字保持不变,因为ProjectStepPathStep正在格式化结果,而不是过滤它们。

注意

与 TinkerPop 不同,Neptune 引擎不会通过以下方式优化性能膨胀其中NeptuneGraphQueryStepNeptuneTraverserConverterStep步骤。膨胀是 TinkerPop 操作,它将遍历器组合在同一顶点上以减少运营开销,这就是导致CountTraversers数字有所不同。由于膨胀只发生在 Neptune 委派给 TinkerPop 的步骤中,而不是在 Neptune 本身处理的步骤中,因此CountTraverser列很少有不同。

“时间” 列报告该步骤所花费的毫秒数,而% Dur列报告了该步骤所花费的总处理时间的百分比。这些指标通过显示花费时间最长的步骤来告诉你在哪里集中调整工作。

Neptune 中的指数运营指标profileAPI 输出

Neptune 配置文件 API 输出中的另一组指标是索引操作:

Index Operations ================ Query execution: # of statement index ops: 23191 # of unique statement index ops: 5960 Duplication ratio: 3.89 # of terms materialized: 0

这些报告:

  • 索引查找的总数。

  • 执行的唯一索引查找的数量。

  • 总索引查找与唯一查找的比率。比率越低表示冗余减少。

  • 从术语词典中实现的术语数。

在 Neptune 中重复指标profileAPI 输出

如果你的穿越使用repeat()步骤如上例所示,那么包含重复指标的部分将显示在profile输出:

Repeat Metrics ============== Iteration Visited Output Until Emit Next ------------------------------------------------------ 0 2 0 0 0 2 1 53 0 0 0 53 2 3856 3856 3856 0 0 ------------------------------------------------------ 3911 3856 3856 0 55

这些报告:

  • 一行的循环计数(Iterationcolumn)。

  • 循环访问的元素数量(Visitedcolumn)。

  • 循环输出的元素数量(Outputcolumn)。

  • 循环输出的最后一个元素(Untilcolumn)。

  • 循环发出的元素数量(Emitcolumn)。

  • 从循环传递到后续循环的元素数量(Nextcolumn)。

这些重复指标非常有助于理解遍历的分支因素,以便了解数据库正在完成多少工作。您可以使用这些数字来诊断性能问题,尤其是当同一遍历对不同参数的执行情况显著不同时。

Neptune 中的全文搜索指标profileAPI 输出

当遍历使用全文搜索如上例所示,将在中查找包含全文搜索 (FTS) 指标的部分。profile输出:

FTS Metrics ============== SearchNode[(idVar=?1, query=Anchora~, field=city) . project ?1 .], {endpoint=your-OpenSearch-endpoint-URL, incomingSolutionsThreshold=1000, estimatedCardinality=INFINITY, remoteCallTimeSummary=[total=65, avg=32.500000, max=37, min=28], remoteCallTime=65, remoteCalls=2, joinTime=0, indexTime=0, remoteResults=2} 2 result(s) produced from SearchNode above

这显示了发送到 ElasticSearch (ES) 集群的查询,并报告了与 ElasticSearch 互动的几个指标,这些指标可以帮助您查明与全文搜索相关的性能问题:

  • 有关对 ElasticSearch 索引的调用的摘要信息:

    • 所有 RemoteCall 满足查询所需的总毫秒数(total)。

    • 在 RemoteCall 上花费的平均毫秒数 (avg)。

    • 在 RemoteCall 中花费的最低毫秒数 (min)。

    • 在 RemoteCall 中花费的最大毫秒数 (max)。

  • RemoteCall 使用 ElasticSearch 的总时间 (remoteCallTime)。

  • 向 ElasticSearch 发出的 RemoteCall 的数量 (remoteCalls)。

  • ElasticSearch 结果的连接所花费的毫秒数(joinTime)。

  • 索引查找所花费的毫秒数(indexTime)。

  • ElasticSearch 返回的结果总数 (remoteResults)。