Como utilizar o passo do perfil de execução para avaliar as consultas do Gremlin

APLICA-SE A: Gremlin

Este artigo fornece uma descrição geral de como utilizar o passo do perfil de execução para bases de dados de gráficos do Azure Cosmos DB para Gremlin. Este passo fornece informações relevantes para a resolução de problemas e otimizações de consulta, e é compatível com qualquer consulta do Gremlin que possa ser executada relativamente a uma conta da API do Gremlin no Cosmos DB.

Para utilizar este passo, basta acrescentar a executionProfile() chamada de função no final da consulta do Gremlin. A consulta do Gremlin será executada e o resultado da operação devolverá um objeto de resposta JSON com o perfil de execução da consulta.

Por exemplo:

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

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

Depois de chamar o executionProfile() passo, a resposta será um objeto JSON que inclui o passo do Gremlin executado, o tempo total que demorou e uma matriz dos operadores de runtime do Cosmos DB em que a instrução resultou.

Nota

Esta implementação para o Perfil de Execução não está definida na especificação do Apache Tinkerpop. É específico do Azure Cosmos DB para a implementação do Gremlin.

Exemplo de Resposta

Segue-se um exemplo anotado do resultado que será devolvido:

Nota

Este exemplo é anotado com comentários que explicam a estrutura geral da resposta. Uma resposta executionProfile real não conterá comentários.

[
  {
    // 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
        }
      }
    ]
  }
]

Nota

O passo executionProfile executará a consulta gremlin. Isto inclui os addV passos ou addE, que resultarão na criação e consolidarão as alterações especificadas na consulta. Como resultado, as Unidades de Pedido geradas pela consulta do Gremlin também serão cobradas.

Objetos de resposta do perfil de execução

A resposta de uma função executionProfile() produzirá uma hierarquia de objetos JSON com a seguinte estrutura:

  • Objeto de operação gremlin: representa toda a operação do Gremlin que foi executada. Contém as seguintes propriedades.

    • gremlin: a instrução explícita do Gremlin que foi executada.
    • totalTime: a hora, em milissegundos, em que a execução do passo incorreu.
    • metrics: uma matriz que contém cada um dos operadores de runtime do Cosmos DB que foram executados para satisfazer a consulta. Esta lista está ordenada por ordem de execução.
  • Operadores de runtime do Cosmos DB: representa cada um dos componentes de toda a operação do Gremlin. Esta lista está ordenada por ordem de execução. Cada objeto contém as seguintes propriedades:

    • name: nome do operador . Este é o tipo de passo que foi avaliado e executado. Leia mais na tabela abaixo.
    • time: período de tempo, em milissegundos, que um determinado operador demorou.
    • annotations: contém informações adicionais, específicas do operador que foi executado.
    • annotations.percentTime: percentagem do tempo total que demorou a executar o operador específico.
    • counts: número de objetos que foram devolvidos a partir da camada de armazenamento por este operador. Isto está contido no counts.resultCount valor escalar no interior.
    • storeOps: representa uma operação de armazenamento que pode abranger uma ou várias partições.
    • storeOps.fanoutFactor: representa o número de partições a que esta operação de armazenamento específica acedeu.
    • storeOps.count: representa o número de resultados devolvidos por esta operação de armazenamento.
    • storeOps.size: representa o tamanho em bytes do resultado de uma determinada operação de armazenamento.
Operador de Runtime do Gremlin do Cosmos DB Descrição
GetVertices Este passo obtém um conjunto predefinido de objetos a partir da camada de persistência.
GetEdges Este passo obtém as arestas adjacentes a um conjunto de vértices. Este passo pode resultar numa ou muitas operações de armazenamento.
GetNeighborVertices Este passo obtém os vértices que estão ligados a um conjunto de arestas. As arestas contêm as chaves de partição e os IDs dos vértices de origem e de destino.
Coalesce Este passo explica a avaliação de duas operações sempre que o passo do coalesce() Gremlin é executado.
CartesianProductOperator Este passo calcula um produto cartesiano entre dois conjuntos de dados. Normalmente executado sempre que os predicados ou from() são utilizadosto().
ConstantSourceOperator Este passo calcula uma expressão para produzir um valor constante como resultado.
ProjectOperator Este passo prepara e serializa uma resposta com o resultado das operações anteriores.
ProjectAggregation Este passo prepara e serializa uma resposta para uma operação de agregação.

Nota

Esta lista continuará a ser atualizada à medida que forem adicionados novos operadores.

Exemplos sobre como analisar uma resposta do perfil de execução

Seguem-se exemplos de otimizações comuns que podem ser detetadas com a resposta do Perfil de Execução:

  • Consulta blind fan-out.
  • Consulta não filtrada.

Padrões de consulta de fan-out cegos

Suponha a seguinte resposta do perfil de execução a partir de um gráfico particionado:

[
  {
    "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
        }
      }
    ]
  }
]

As seguintes conclusões podem ser feitas a partir da mesma:

  • A consulta é uma pesquisa de ID única, uma vez que a instrução Gremlin segue o padrão g.V('id').
  • A julgar pela time métrica, a latência desta consulta parece ser elevada, uma vez que são mais de 10 ms para uma operação de leitura de ponto único.
  • Se analisarmos o storeOps objeto, podemos ver que é 5, o fanoutFactor que significa que 5 partições foram acedidas por esta operação.

Como conclusão desta análise, podemos determinar que a primeira consulta está a aceder a mais partições do que o necessário. Isto pode ser resolvido ao especificar a chave de criação de partições na consulta como um predicado. Isto levará a menos latência e menos custos por consulta. Saiba mais sobre a criação de partições de grafos. Uma consulta mais ideal seria g.V('tt0093640').has('partitionKey', 't1001').

Padrões de consulta não filtradas

Compare as duas respostas do perfil de execução seguintes. Para simplificar, estes exemplos utilizam um único gráfico particionado.

Esta primeira consulta obtém todos os vértices com a etiqueta tweet e, em seguida, obtém os vértices vizinhos:

[
  {
    "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
        }
      }
    ]
  }
]

Repare no perfil da mesma consulta, mas agora com um filtro adicional, has('lang', 'en'), antes de explorar os vértices adjacentes:

[
  {
    "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
        }
      }
    ]
  }
]

No entanto, estas duas consultas atingiram o mesmo resultado. No entanto, a primeira exigirá mais Unidades de Pedido, uma vez que precisava de iterar um conjunto de dados inicial maior antes de consultar os itens adjacentes. Podemos ver indicadores deste comportamento ao comparar os seguintes parâmetros de ambas as respostas:

  • O metrics[0].time valor é superior na primeira resposta, o que indica que este único passo demorou mais tempo a ser resolvido.
  • O metrics[0].counts.resultsCount valor também é superior na primeira resposta, o que indica que o conjunto de dados de trabalho inicial foi maior.

Passos seguintes