Come usare il passaggio del profilo di esecuzione per valutare le query Gremlin

SI APPLICA A: Gremlin

Questo articolo offre una panoramica di come usare il passaggio del profilo di esecuzione per i database del grafico Gremlin di Azure Cosmos DB. Questo passaggio fornisce le informazioni rilevanti per la risoluzione dei problemi e le ottimizzazioni delle query ed è compatibile con tutte le query Gremlin eseguibili in un account dell'API Gremlin di Cosmos DB.

Per usare questo passaggio, aggiungere semplicemente la chiamata alla executionProfile() funzione alla fine della query Gremlin. La query Gremlin verrà eseguita e il risultato dell'operazione restituirà un oggetto risposta JSON con il profilo di esecuzione della query.

Ad esempio:

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

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

Dopo aver chiamato il executionProfile() passaggio, la risposta sarà un oggetto JSON che include il passaggio Gremlin eseguito, il tempo totale necessario e una matrice degli operatori di runtime di Cosmos DB che l'istruzione ha generato.

Nota

Questa implementazione per Il profilo di esecuzione non è definita nella specifica Apache Tinkerpop. È specifico dell'implementazione di Azure Cosmos DB per l'implementazione di Gremlin.

Esempio di risposta

Di seguito è riportato un esempio annotato dell'output che verrà restituito:

Nota

Questo esempio viene annotato con commenti che spiegano la struttura generale della risposta. Una risposta executionProfile effettiva non contiene commenti.

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

Il passaggio executionProfile eseguirà la query Gremlin. Include i addV passaggi o addE, che determinano la creazione e eseguiranno il commit delle modifiche specificate nella query. Di conseguenza, le unità richiesta generate dalla query Gremlin verranno addebitate anche.

Oggetti di risposta del profilo di esecuzione

La risposta di una funzione executionProfile() restituirà una gerarchia di oggetti JSON con la struttura seguente:

  • Oggetto operazione Gremlin: rappresenta l'intera operazione Gremlin eseguita. Contiene le proprietà seguenti.

    • gremlin: istruzione Gremlin esplicita eseguita.
    • totalTime: tempo, in millisecondi, in cui l'esecuzione del passaggio è stata eseguita.
    • metrics: matrice che contiene ognuno degli operatori di runtime di Cosmos DB eseguiti per soddisfare la query. Questo elenco viene ordinato in ordine di esecuzione.
  • Operatori di runtime di Cosmos DB: rappresenta ognuno dei componenti dell'intera operazione Gremlin. Questo elenco viene ordinato in ordine di esecuzione. Ogni oggetto contiene le proprietà seguenti:

    • name: nome dell'operatore. Questo è il tipo di passaggio che è stato valutato ed eseguito. Altre informazioni nella tabella seguente.
    • time: quantità di tempo, in millisecondi, che un determinato operatore ha impiegato.
    • annotations: contiene informazioni aggiuntive, specifiche dell'operatore eseguito.
    • annotations.percentTime: percentuale del tempo totale necessario per eseguire l'operatore specifico.
    • counts: numero di oggetti restituiti dal livello di archiviazione da questo operatore. Questo valore è contenuto nel counts.resultCount valore scalare all'interno.
    • storeOps: rappresenta un'operazione di archiviazione che può estendersi su una o più partizioni.
    • storeOps.fanoutFactor: rappresenta il numero di partizioni a cui è stato eseguito l'accesso a questa operazione di archiviazione specifica.
    • storeOps.count: rappresenta il numero di risultati restituiti dall'operazione di archiviazione.
    • storeOps.size: rappresenta le dimensioni in byte del risultato di un'operazione di archiviazione specificata.
Operatore Gremlin Gremlin di Cosmos DB Descrizione
GetVertices Questo passaggio ottiene un set predicato di oggetti dal livello di persistenza.
GetEdges Questo passaggio ottiene i bordi adiacenti a un set di vertici. Questo passaggio può comportare una o più operazioni di archiviazione.
GetNeighborVertices Questo passaggio ottiene i vertici connessi a un set di bordi. I bordi contengono le chiavi di partizione e l'ID dei vertici di origine e destinazione.
Coalesce Questo passaggio consente di valutare due operazioni ogni volta che viene eseguito il coalesce() passaggio Gremlin.
CartesianProductOperator Questo passaggio calcola un prodotto cartesiano tra due set di dati. In genere viene eseguito ogni volta che vengono usati i predicati to() o from() .
ConstantSourceOperator Questo passaggio calcola un'espressione per produrre un valore costante di conseguenza.
ProjectOperator Questo passaggio prepara e serializza una risposta usando il risultato delle operazioni precedenti.
ProjectAggregation Questo passaggio prepara e serializza una risposta per un'operazione di aggregazione.

Nota

Questo elenco continuerà a essere aggiornato man mano che vengono aggiunti nuovi operatori.

Esempi su come analizzare una risposta del profilo di esecuzione

Di seguito sono riportati esempi di ottimizzazioni comuni che possono essere individuate usando la risposta del profilo di esecuzione:

  • Query di fan-out cieco.
  • Query non filtrata.

Modelli di query di fan-out cieco

Si supponga la risposta del profilo di esecuzione seguente da un grafico partizionato:

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

Le conclusioni seguenti possono essere effettuate da esso:

  • La query è una singola ricerca ID, poiché l'istruzione Gremlin segue il modello g.V('id').
  • A giudicare dalla time metrica, la latenza di questa query sembra essere elevata poiché è più di 10ms per un'operazione di lettura a un singolo punto.
  • Se si esamina l'oggetto storeOps , è possibile notare che è fanoutFactor5, che significa che 5 partizioni sono state accessibili da questa operazione.

Come conclusione di questa analisi, è possibile determinare che la prima query accede a più partizioni rispetto alle esigenze. Questo può essere risolto specificando la chiave di partizionamento nella query come predicato. Ciò comporta una minore latenza e un costo inferiore per ogni query. Per altre informazioni, vedere l'articolo sul partizionamento di grafi. Una query più ottimale sarebbe g.V('tt0093640').has('partitionKey', 't1001').

Modelli di query non filtrati

Confrontare le due risposte del profilo di esecuzione seguenti. Per semplicità, questi esempi usano un singolo grafico partizionato.

Questa prima query recupera tutti i vertici con l'etichetta tweet e quindi ottiene i vertici adiacenti:

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

Si noti il profilo della stessa query, ma ora con un filtro aggiuntivo, has('lang', 'en'), prima di esplorare i vertici adiacenti:

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

Queste due query hanno raggiunto lo stesso risultato, tuttavia, la prima richiederà più unità richiesta perché è necessario eseguire l'iterazione di un set di dati iniziale più grande prima di eseguire query sugli elementi adiacenti. È possibile visualizzare gli indicatori di questo comportamento quando si confrontano i parametri seguenti da entrambe le risposte:

  • Il metrics[0].time valore è più alto nella prima risposta, che indica che questo singolo passaggio ha richiesto più tempo per risolvere.
  • Il metrics[0].counts.resultsCount valore è superiore anche nella prima risposta, che indica che il set di dati di lavoro iniziale è maggiore.

Passaggi successivi