How to use the execution profile step to evaluate your Gremlin queries (Использование шага профиля выполнения для анализа запросов Gremlin)

ПРИМЕНИМО К: Гремлин

В этой статье представлен обзор использования шага профиля выполнения для Azure Cosmos DB для графовых баз данных Gremlin. На этом шаге предоставляются важные сведения для устранения неполадок и оптимизации запросов, а также обеспечивается совместимость с любым запросом Gremlin, который можно выполнить для учетной записи API Gremlin в Cosmos DB.

Чтобы использовать этот шаг, просто добавьте вызов функции executionProfile() в конец запроса Gremlin. Будет выполнен запрос 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. Он предназначен для реализации Gremlin в Azure Cosmos DB.

Пример ответа

Ниже приведен пример (с комментариями) выходных данных, которые будут возвращены:

Примечание

Комментарии в этом примере поясняют общую структуру ответа. Фактический ответ 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. В него входят шаги addV и addE, которые ведут к созданию и фиксации изменений, указанных в запросе. В результате также будет начислена плата за единицы запроса, созданные запросом 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: представляет размер в байтах результата данной операции с хранилищем.
Оператор среды выполнения Gremlin Cosmos DB Описание
GetVertices Этот шаг получает заданный набор объектов из уровня сохраняемости.
GetEdges Этот шаг получает ребра, смежные с набором вершин. Этот шаг может привести к выполнению одной или нескольких операций с хранилищем.
GetNeighborVertices Этот шаг получает вершины, прилегающие к набору ребер. Ребра содержат ключи секций и идентификаторы их исходной и целевой вершин.
Coalesce На этом шаге выполняется вычисление двух операций при каждом выполнении шага Gremlin coalesce().
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
        }
      }
    ]
  }
]

Из него можно сделать следующие выводы:

  • Запрос представляет собой одиночную операцию поиска идентификатора, так как инструкция Gremlin соответствует шаблону g.V('id').
  • Судя по метрике time, задержка этого запроса кажется большой, так как она составляет больше 10 мс для одной точечной операции чтения.
  • Если рассмотреть объект storeOps, можно увидеть, что fanoutFactor имеет значение 5, а это означает, что данная операция обращалась к 5 секциям.

По результатам этого анализа можно сказать, что первый запрос обращается к большему числу секций, чем необходимо. Эту проблему можно устранить, указав в запросе ключ секционирования в виде предиката. Это приведет к сокращению задержки и снижению затрат на запрос. Узнайте больше о секционировании графов. Более оптимальным является запрос 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 в первом ответе также выше, и это означает, что начальный рабочий набор данных был больше.

Дальнейшие действия