Jak użyć kroku profilu wykonywania do oceny zapytań aparatu Gremlin

DOTYCZY: Gremlin

Ten artykuł zawiera omówienie sposobu korzystania z kroku profilu wykonywania dla baz danych grafów Języka Gremlin w usłudze Azure Cosmos DB. Ten krok zawiera istotne informacje dotyczące rozwiązywania problemów i optymalizacji zapytań oraz jest zgodny z dowolnymi zapytaniami Gremlin, które można wykonać względem konta interfejsu API Gremlin usługi Cosmos DB.

Aby użyć tego kroku, po prostu dołącz executionProfile() wywołanie funkcji na końcu zapytania Gremlin. Zapytanie Gremlin zostanie wykonane , a wynik operacji zwróci obiekt odpowiedzi JSON z profilem wykonywania zapytania.

Przykład:

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

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

Po wywołaniu executionProfile() kroku odpowiedź będzie obiektem JSON obejmującym wykonany krok języka Gremlin, łączny czas i tablicę operatorów środowiska uruchomieniowego usługi Cosmos DB, które spowodowały wykonanie instrukcji.

Uwaga

Ta implementacja profilu wykonywania nie jest zdefiniowana w specyfikacji apache Tinkerpop. Jest ona specyficzna dla usługi Azure Cosmos DB dla implementacji języka Gremlin.

Przykład odpowiedzi

Poniżej znajduje się przykład z adnotacjami danych wyjściowych, które zostaną zwrócone:

Uwaga

Ten przykład jest oznaczony adnotacjami z komentarzami, które wyjaśniają ogólną strukturę odpowiedzi. Rzeczywista odpowiedź executionProfile nie będzie zawierać żadnych komentarzy.

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

Uwaga

Krok executionProfile spowoduje wykonanie zapytania Gremlin. addV Obejmuje to kroki lubaddE, które spowodują utworzenie i zatwierdzą zmiany określone w zapytaniu. W związku z tym opłaty będą również naliczane za jednostki żądań wygenerowane przez zapytanie Gremlin.

Obiekty odpowiedzi profilu wykonywania

Odpowiedź funkcji executionProfile() zwróci hierarchię obiektów JSON o następującej strukturze:

  • Obiekt operacji języka Gremlin: reprezentuje całą wykonaną operację języka Gremlin. Zawiera następujące właściwości.

    • gremlin: jawna instrukcja Języka Gremlin, która została wykonana.
    • totalTime: czas w milisekundach, w których wykonano krok.
    • metrics: Tablica zawierająca każdy operator środowiska uruchomieniowego usługi Cosmos DB, który został wykonany w celu spełnienia zapytania. Ta lista jest sortowana w kolejności wykonywania.
  • Operatory środowiska uruchomieniowego usługi Cosmos DB: reprezentuje każdy ze składników całej operacji języka Gremlin. Ta lista jest sortowana w kolejności wykonywania. Każdy obiekt zawiera następujące właściwości:

    • name: nazwa operatora. Jest to typ kroku, który został oceniony i wykonany. Przeczytaj więcej w poniższej tabeli.
    • time: ilość czasu (w milisekundach) zajęła dany operator.
    • annotations: zawiera dodatkowe informacje specyficzne dla wykonanego operatora.
    • annotations.percentTime: procent całkowitego czasu, jaki upłynął do wykonania określonego operatora.
    • counts: liczba obiektów zwróconych z warstwy magazynu przez ten operator. Jest to zawarte w wartości skalarnych counts.resultCount wewnątrz.
    • storeOps: reprezentuje operację magazynu, która może obejmować jedną lub wiele partycji.
    • storeOps.fanoutFactor: reprezentuje liczbę partycji, do których uzyskiwano dostęp do tej konkretnej operacji magazynu.
    • storeOps.count: reprezentuje liczbę wyników zwróconych przez tę operację magazynu.
    • storeOps.size: reprezentuje rozmiar w bajtach wyniku danej operacji magazynu.
Operator środowiska uruchomieniowego języka Gremlin w usłudze Cosmos DB Opis
GetVertices Ten krok uzyskuje wstępnie określony zestaw obiektów z warstwy trwałości.
GetEdges Ten krok uzyskuje krawędzie sąsiadujące z zestawem wierzchołków. Ten krok może spowodować wykonanie jednej lub wielu operacji magazynowania.
GetNeighborVertices Ten krok uzyskuje wierzchołki połączone z zestawem krawędzi. Krawędzie zawierają klucze partycji i identyfikatory ich źródłowych i docelowych wierzchołków.
Coalesce Ten krok obejmuje ocenę dwóch operacji przy każdym wykonaniu coalesce() kroku języka Gremlin.
CartesianProductOperator Ten krok oblicza produkt kartezjański między dwoma zestawami danych. Zwykle wykonywane za każdym razem, gdy predykaty to() lub from() są używane.
ConstantSourceOperator Ten krok oblicza wyrażenie w celu wygenerowania stałej wartości w wyniku.
ProjectOperator Ten krok przygotowuje i serializuje odpowiedź przy użyciu wyniku poprzednich operacji.
ProjectAggregation Ten krok przygotowuje i serializuje odpowiedź na operację agregacji.

Uwaga

Ta lista będzie nadal aktualizowana w miarę dodawania nowych operatorów.

Przykłady analizowania odpowiedzi profilu wykonywania

Poniżej przedstawiono przykłady typowych optymalizacji, które można wykryć przy użyciu odpowiedzi profilu wykonywania:

  • Ślepe zapytanie fan-out.
  • Niefiltrowane zapytanie.

Ślepe wzorce zapytań fan-out

Załóżmy, że następująca odpowiedź profilu wykonywania z partycjonowanego grafu:

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

Z tego można wyciągnąć następujące wnioski:

  • Zapytanie jest pojedynczym wyszukiwaniem identyfikatorów, ponieważ instrukcja Gremlin jest zgodna ze wzorcem g.V('id').
  • Sądząc po time metryce, opóźnienie tego zapytania wydaje się być wysokie, ponieważ jest to ponad 10 ms dla pojedynczej operacji odczytu punktowego.
  • Jeśli przyjrzymy się obiektowi storeOps , zobaczymy, że obiekt fanoutFactor to 5, co oznacza, że do tej operacji uzyskiwano dostęp do 5 partycji .

Podsumowując tę analizę, możemy określić, że pierwsze zapytanie uzyskuje dostęp do większej liczby partycji niż jest to konieczne. Można to rozwiązać, określając klucz partycjonowania w zapytaniu jako predykat. Doprowadzi to do mniejszego opóźnienia i mniejszego kosztu zapytania. Dowiedz się więcej o partycjonowaniu grafu. Bardziej optymalne zapytanie to g.V('tt0093640').has('partitionKey', 't1001').

Niefiltrowane wzorce zapytań

Porównaj następujące dwie odpowiedzi profilu wykonywania. Dla uproszczenia w tych przykładach użyto pojedynczego wykresu partycjonowanego.

To pierwsze zapytanie pobiera wszystkie wierzchołki z etykietą tweet , a następnie uzyskuje sąsiadujące wierzchołki:

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

Zwróć uwagę na profil tego samego zapytania, ale teraz z dodatkowym filtrem , has('lang', 'en')przed eksplorowanie sąsiednich wierzchołków:

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

Te dwa zapytania osiągnęły ten sam wynik, jednak pierwsza z nich będzie wymagać większej liczby jednostek żądania, ponieważ wymaga iteracji większego początkowego zestawu danych przed wykonaniem zapytania względem sąsiednich elementów. Wskaźniki tego zachowania można zobaczyć podczas porównywania następujących parametrów z obu odpowiedzi:

  • Wartość metrics[0].time jest wyższa w pierwszej odpowiedzi, co oznacza, że ten pojedynczy krok trwał dłużej, aby rozwiązać problem.
  • Wartość metrics[0].counts.resultsCount jest wyższa, jak również w pierwszej odpowiedzi, co oznacza, że początkowy roboczy zestaw danych był większy.

Następne kroki