Condividi tramite


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 a grafo di Azure Cosmos DB per Gremlin. 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 di funzione executionProfile() 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 passaggio executionProfile(), la risposta sarà un oggetto JSON che include il passaggio Gremlin eseguito, il tempo totale impiegato e una matrice degli operatori di runtime di Cosmos DB risultanti dall'istruzione.

Nota

Questa implementazione per Il profilo di esecuzione non è definita nella specifica Apache Tinkerpop. È specifico 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 conterrà 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. Sono inclusi i passaggi addV o addE, che comportano la creazione e il commit delle modifiche specificate nella query. Di conseguenza, verranno addebitate anche le unità richiesta generate dalla query Gremlin.

Oggetti 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, espresso in millisecondi, in cui si verifica l'esecuzione del passaggio.
    • 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. Si tratta del tipo di passaggio valutato ed eseguito. Per altre informazioni, vedere la 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 impiegato per eseguire l'operatore specifico.
    • counts: numero di oggetti restituiti dal livello di archiviazione da questo operatore. È inclusa nel valore scalare counts.resultCount 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 per questa specifica operazione di archiviazione.
    • storeOps.count: rappresenta il numero di risultati restituiti da questa operazione di archiviazione.
    • storeOps.size: rappresenta le dimensioni in byte del risultato di una determinata operazione di archiviazione.
Operatore di runtime 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 collegati a un set di bordi. I bordi contengono le chiavi di partizione e l'ID dei vertici di origine e di destinazione.
Coalesce Questo passaggio consente di valutare due operazioni ogni volta che viene eseguito il passaggio Gremlin coalesce().
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 come risultato.
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 è possibile individuare usando la risposta del profilo di esecuzione:

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

Modelli di query di tipo fan-out cieco

Si supponga la risposta del profilo di esecuzione seguente da un grafo 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
        }
      }
    ]
  }
]

È possibile trarre le conclusioni seguenti:

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

Come conclusione di questa analisi, è possibile determinare che la prima query accede a più partizioni del necessario. Il problema può essere risolto specificando la chiave di partizionamento nella query come predicato. Ciò comporterà 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 filtrate

Confrontare le due risposte del profilo di esecuzione seguenti. Per semplicità, questi esempi usano un singolo grafo 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, ma 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 valore metrics[0].time è maggiore nella prima risposta, il che indica che questo singolo passaggio ha richiesto più tempo per la risoluzione.
  • Il valore metrics[0].counts.resultsCount è maggiore nella prima risposta, il che indica che il set di dati di lavoro iniziale è maggiore.

Passaggi successivi