如何使用执行配置文件步骤来评估 Gremlin 查询

适用对象: Gremlin

本文概述了如何为 Azure Cosmos DB for Gremlin 图形数据库使用“执行配置文件”步骤。 此步骤提供有关疑难解答和查询优化的相关信息,并且它与可针对 Cosmos DB Gremlin API 帐户执行的任何 Gremlin 查询兼容。

若要使用此步骤,只需在 Gremlin 查询的末尾追加 executionProfile() 函数调用即可。 将执行你的 Gremlin 查询,操作结果将返回包含查询执行配置文件的 JSON 响应对象。

例如:

    // Basic traversal
    g.V('mary').out()

    // Basic traversal with execution profile call
    g.V('mary').out().executionProfile()

调用 executionProfile() 步骤后,响应将是一个 JSON 对象,其中包含执行的 Gremlin 步骤、该步骤花费的总时间,以及语句生成的 Cosmos DB 运行时运算符数组。

注意

Apache Tinkerpop 规范中未定义此执行配置文件实现。 它是特定于 Azure Cosmos DB for Gremlin 的实现。

响应示例

下面是将会返回的带批注的输出示例:

注意

此示例使用注释做了批注,以便解释响应的常规结构。 实际的 executionProfile 响应不包含任何注释。

[
  {
    // The Gremlin statement that was executed.
    "gremlin": "g.V('mary').out().executionProfile()",

    // Amount of time in milliseconds that the entire operation took.
    "totalTime": 28,

    // An array containing metrics for each of the steps that were executed. 
    // Each Gremlin step will translate to one or more of these steps.
    // This list is sorted in order of execution.
    "metrics": [
      {
        // This operation obtains a set of Vertex objects.
        // The metrics include: time, percentTime of total execution time, resultCount, 
        // fanoutFactor, count, size (in bytes) and time.
        "name": "GetVertices",
        "time": 24,
        "annotations": {
          "percentTime": 85.71
        },
        "counts": {
          "resultCount": 2
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 2,
            "size": 696,
            "time": 0.4
          }
        ]
      },
      {
        // This operation obtains a set of Edge objects. 
        // Depending on the query, these might be directly adjacent to a set of vertices, 
        // or separate, in the case of an E() query.
        //
        // The metrics include: time, percentTime of total execution time, resultCount, 
        // fanoutFactor, count, size (in bytes) and time.
        "name": "GetEdges",
        "time": 4,
        "annotations": {
          "percentTime": 14.29
        },
        "counts": {
          "resultCount": 1
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 1,
            "size": 419,
            "time": 0.67
          }
        ]
      },
      {
        // This operation obtains the vertices that a set of edges point at.
        // The metrics include: time, percentTime of total execution time and resultCount.
        "name": "GetNeighborVertices",
        "time": 0,
        "annotations": {
          "percentTime": 0
        },
        "counts": {
          "resultCount": 1
        }
      },
      {
        // This operation represents the serialization and preparation for a result from 
        // the preceding graph operations. The metrics include: time, percentTime of total 
        // execution time and resultCount.
        "name": "ProjectOperator",
        "time": 0,
        "annotations": {
          "percentTime": 0
        },
        "counts": {
          "resultCount": 1
        }
      }
    ]
  }
]

注意

executionProfile 步骤将执行 Gremlin 查询。 此查询包含 addVaddE 步骤,这些步骤会完成创建过程,并提交查询中指定的更改。 因此,Gremlin 查询生成的请求单位也会产生费用。

执行配置文件响应对象

executionProfile() 函数的响应将生成采用以下结构的 JSON 对象层次结构:

  • Gremlin 操作对象:表示已执行的整个 Gremlin 操作。 包含以下属性。

    • gremlin:已执行的显式 Gremlin 语句。
    • totalTime:执行该步骤所花费的时间(以毫秒为单位)。
    • metrics:一个数组,其中包含为了完成查询而执行的每个 Cosmos DB 运行时运算符。 此列表已按执行顺序排序。
  • Cosmos DB 运行时运算符:表示完整 Gremlin 运算的每个组件。 此列表已按执行顺序排序。 每个对象包含以下属性:

    • name:运算符的名称。 这是已评估和执行的步骤的类型。 请在下表中了解详细信息。
    • time:给定的运算符所花费的时间(以毫秒为单位)。
    • annotations:包含特定于已执行的运算符的其他信息。
    • annotations.percentTime:执行特定运算符所用总时间的百分比。
    • counts:此运算符从存储层返回的对象数。 此值包含在内部的 counts.resultCount 标量值中。
    • storeOps:表示可以跨一个或多个分区的存储操作。
    • storeOps.fanoutFactor:表示此特定存储操作访问的分区数。
    • storeOps.count:表示此存储操作返回的结果数。
    • storeOps.size:表示给定存储操作的结果的大小(以字节为单位)。
Cosmos DB Gremlin 运行时运算符 说明
GetVertices 此步骤从持久性层获取一组带谓词的对象。
GetEdges 此步骤获取与一组顶点相邻的边缘。 此步骤可以生成一个或多个存储操作。
GetNeighborVertices 此步骤获取与一组边缘相连接的顶点。 边缘包含分区键及其源和目标顶点的 ID。
Coalesce 每当执行 coalesce() Gremlin 步骤时,此步骤都会考虑两项操作的评估结果。
CartesianProductOperator 此步骤计算两个数据集之间的笛卡儿积。 通常,每当使用谓词 to()from(),都会执行此步骤。
ConstantSourceOperator 此步骤计算一个表达式,以生成一个常量值作为结果。
ProjectOperator 此步骤使用先前操作的结果来准备并序列化响应。
ProjectAggregation 此步骤准备并序列化聚合操作的响应。

注意

随着新运算符的添加,此列表将不断更新。

有关如何分析执行配置文件响应的示例

下面是可以使用执行配置文件响应发现的常用优化方法示例:

  • 盲目扇出查询。
  • 未筛选的查询。

盲目扇出查询模式

假设某个分区图形返回了以下执行配置文件响应:

[
  {
    "gremlin": "g.V('tt0093640').executionProfile()",
    "totalTime": 46,
    "metrics": [
      {
        "name": "GetVertices",
        "time": 46,
        "annotations": {
          "percentTime": 100
        },
        "counts": {
          "resultCount": 1
        },
        "storeOps": [
          {
            "fanoutFactor": 5,
            "count": 1,
            "size": 589,
            "time": 75.61
          }
        ]
      },
      {
        "name": "ProjectOperator",
        "time": 0,
        "annotations": {
          "percentTime": 0
        },
        "counts": {
          "resultCount": 1
        }
      }
    ]
  }
]

可以从中得出以下结论:

根据此分析的结论,我们可以确定,第一个查询不必要地访问了多余的分区。 在查询中指定分区键作为谓词可以解决此问题。 这样可以降低延迟以及每个查询的开销。 详细了解图形分区。 更佳的查询是 g.V('tt0093640').has('partitionKey', 't1001')

未筛选的查询模式

比较以下两个执行配置文件响应。 为简单起见,这些示例使用了单个分区图形。

这第一个查询检索所有带有 tweet 标签的顶点,然后获取其相邻顶点:

[
  {
    "gremlin": "g.V().hasLabel('tweet').out().executionProfile()",
    "totalTime": 42,
    "metrics": [
      {
        "name": "GetVertices",
        "time": 31,
        "annotations": {
          "percentTime": 73.81
        },
        "counts": {
          "resultCount": 30
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 13,
            "size": 6819,
            "time": 1.02
          }
        ]
      },
      {
        "name": "GetEdges",
        "time": 6,
        "annotations": {
          "percentTime": 14.29
        },
        "counts": {
          "resultCount": 18
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 20,
            "size": 7950,
            "time": 1.98
          }
        ]
      },
      {
        "name": "GetNeighborVertices",
        "time": 5,
        "annotations": {
          "percentTime": 11.9
        },
        "counts": {
          "resultCount": 20
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 4,
            "size": 1070,
            "time": 1.19
          }
        ]
      },
      {
        "name": "ProjectOperator",
        "time": 0,
        "annotations": {
          "percentTime": 0
        },
        "counts": {
          "resultCount": 20
        }
      }
    ]
  }
]

在浏览相邻顶点之前,请注意同一查询(但现在它具有附加的筛选器 has('lang', 'en'))的配置文件:

[
  {
    "gremlin": "g.V().hasLabel('tweet').has('lang', 'en').out().executionProfile()",
    "totalTime": 14,
    "metrics": [
      {
        "name": "GetVertices",
        "time": 14,
        "annotations": {
          "percentTime": 58.33
        },
        "counts": {
          "resultCount": 11
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 11,
            "size": 4807,
            "time": 1.27
          }
        ]
      },
      {
        "name": "GetEdges",
        "time": 5,
        "annotations": {
          "percentTime": 20.83
        },
        "counts": {
          "resultCount": 18
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 18,
            "size": 7159,
            "time": 1.7
          }
        ]
      },
      {
        "name": "GetNeighborVertices",
        "time": 5,
        "annotations": {
          "percentTime": 20.83
        },
        "counts": {
          "resultCount": 18
        },
        "storeOps": [
          {
            "fanoutFactor": 1,
            "count": 4,
            "size": 1070,
            "time": 1.01
          }
        ]
      },
      {
        "name": "ProjectOperator",
        "time": 0,
        "annotations": {
          "percentTime": 0
        },
        "counts": {
          "resultCount": 18
        }
      }
    ]
  }
]

这两个查询达到了相同的效果,但是,第一个查询需要更多的请求单位,因为它在查询相邻项之前,需要迭代一个更大的初始数据集。 比较两个响应中的以下参数时,可以看到此行为的指示器:

  • 第一个响应中的 metrics[0].time 值更大,表示解决此步骤所花费的时间更长。
  • 第一个响应中的 metrics[0].counts.resultsCount 值也更大,表示初始工作数据集更大。

后续步骤