Utiliser l'explication des requêtes

Query Explain vous permet d'envoyer des requêtes en mode Datastore au backend et recevoir des statistiques de performances détaillées sur l'exécution de la requête du backend en retour. Il fonctionne comme l'opération EXPLAIN ANALYZE dans de nombreux systèmes de bases de données relationnelles.

Vous pouvez envoyer des requêtes d'explication de requête à l'aide du Bibliothèques clientes en mode Datastore.

Les résultats de la fonctionnalité Query Explain vous aident à comprendre comment vos requêtes sont exécutées, vous montrant les inefficacités et l'emplacement des éventuels goulots d'étranglement côté serveur.

Explication de la requête:

  • Fournit des insights sur la phase de planification afin que vous puissiez ajuster vos index de requêtes et améliorer l'efficacité.
  • Il vous aide à comprendre vos coûts et vos performances pour chaque requête. vous permet d'itérer rapidement les différents schémas de requête optimiser leur utilisation.

Comprendre les options Query Explain : "default" et "analyze"

Les opérations Query Explain peuvent être effectuées à l'aide du l'option default ou l'option analyze.

Avec l'option par défaut, l'explication de la requête planifie la requête, mais ignore l'étape d'exécution. Vous obtiendrez alors des informations sur l'étape de planification. Vous pouvez utilisez-le pour vérifier qu'une requête dispose des index nécessaires et identifier sont utilisés. Vous pourrez ainsi vérifier, par exemple, qu'un une requête particulière utilise un index composite plutôt que de devoir croiser plusieurs différents index.

Avec l'option d'analyse, Query Explain génère les deux plans et exécute la requête. Cette commande renvoie toutes les informations du planificateur mentionnées précédemment, ainsi que les statistiques de l'environnement d'exécution de l'exécution de la requête. Cela inclura des informations de facturation ainsi que des insights au niveau du système sur l'exécution des requêtes. Vous pouvez utiliser ces outils pour tester différentes configurations de requêtes et d'index afin d'optimiser leur coût et leur latence.

Quel est le coût de l'explication des requêtes ?

Lorsqu'une requête est expliquée avec l'option par défaut, aucune opération d'indexation ni de lecture n'est effectuée. Quelle que soit la complexité de la requête, une opération de lecture est facturée.

Lorsqu'une requête est expliquée avec l'option d'analyse, des opérations d'indexation et de lecture sont effectuées. La requête vous est donc facturée comme d'habitude. Aucune charge supplémentaire n'est appliquée pour l'activité d'analyse, mais uniquement les frais habituels pour la requête exécutée.

Exécuter une requête avec l'option par défaut

Vous pouvez utiliser une bibliothèque cliente pour envoyer une requête d'option par défaut.

Notez que les résultats d'explication des requêtes sont authentifiés avec Identity and Access Management, à l'aide des mêmes autorisations que pour les opérations de requêtes standards.

Java

Pour savoir comment installer et utiliser la bibliothèque cliente pour le mode Datastore, consultez la section Bibliothèques clientes en mode Datastore. Pour en savoir plus, consultez les API Java du mode Datastore documentation de référence.

Pour vous authentifier auprès du mode Datastore, configurez les identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement local.


import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.Query;
import com.google.cloud.datastore.QueryResults;
import com.google.cloud.datastore.models.ExplainMetrics;
import com.google.cloud.datastore.models.ExplainOptions;
import com.google.cloud.datastore.models.PlanSummary;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class QueryProfileExplain {
  public static void invoke() throws Exception {
    // Instantiates a client
    Datastore datastore = DatastoreOptions.getDefaultInstance().getService();

    // Build the query
    Query<Entity> query = Query.newEntityQueryBuilder().setKind("Task").build();

    // Set the explain options to get back *only* the plan summary
    QueryResults<Entity> results = datastore.run(query, ExplainOptions.newBuilder().build());

    // Get the explain metrics
    Optional<ExplainMetrics> explainMetrics = results.getExplainMetrics();
    if (!explainMetrics.isPresent()) {
      throw new Exception("No explain metrics returned");
    }
    PlanSummary planSummary = explainMetrics.get().getPlanSummary();
    List<Map<String, Object>> indexesUsed = planSummary.getIndexesUsed();
    System.out.println("----- Indexes Used -----");
    indexesUsed.forEach(map -> map.forEach((key, val) -> System.out.println(key + ": " + val)));
  }
}

Consultez le champ indexes_used dans la réponse pour en savoir plus sur les index utilisés dans le plan de requête :

"indexes_used": [
        {"query_scope": "Collection Group", "properties": "(__name__ ASC)"},
]

Pour en savoir plus sur ce rapport, consultez la référence au rapport.

Exécuter une requête avec l'option d'analyse

Vous pouvez utiliser une bibliothèque cliente pour envoyer une requête d'option par défaut.

Notez que les résultats de l'analyse des requêtes sont authentifiés avec Identity and Access Management (IAM), en utilisant les mêmes autorisations pour les opérations de requête normales.

Java

Pour savoir comment installer et utiliser la bibliothèque cliente pour le mode Datastore, consultez Bibliothèques clientes en mode Datastore. Pour en savoir plus, consultez la documentation de référence de l'API Java en mode Datastore.

Pour vous authentifier auprès du mode Datastore, configurez les identifiants par défaut de l'application. Pour en savoir plus, consultez Configurer l'authentification pour un environnement de développement local.

import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.Query;
import com.google.cloud.datastore.QueryResults;
import com.google.cloud.datastore.models.ExecutionStats;
import com.google.cloud.datastore.models.ExplainMetrics;
import com.google.cloud.datastore.models.ExplainOptions;
import com.google.cloud.datastore.models.PlanSummary;
import java.util.List;
import java.util.Map;

public class QueryProfileExplainAnalyze {
  public static void invoke() throws Exception {
    // Instantiates a client
    Datastore datastore = DatastoreOptions.getDefaultInstance().getService();

    // Build the query
    Query<Entity> query = Query.newEntityQueryBuilder().setKind("Task").build();

    // Set explain options with analzye = true to get back the query stats, plan info, and query
    // results
    QueryResults<Entity> results =
        datastore.run(query, ExplainOptions.newBuilder().setAnalyze(true).build());

    // Get the result set stats
    if (!results.getExplainMetrics().isPresent()) {
      throw new Exception("No explain metrics returned");
    }
    ExplainMetrics explainMetrics = results.getExplainMetrics().get();

    // Get the execution stats
    if (!explainMetrics.getExecutionStats().isPresent()) {
      throw new Exception("No execution stats returned");
    }

    ExecutionStats executionStats = explainMetrics.getExecutionStats().get();
    Map<String, Object> debugStats = executionStats.getDebugStats();
    System.out.println("----- Debug Stats -----");
    debugStats.forEach((key, val) -> System.out.println(key + ": " + val));
    System.out.println("----------");

    long resultsReturned = executionStats.getResultsReturned();
    System.out.println("Results returned: " + resultsReturned);

    // Get the plan summary
    PlanSummary planSummary = explainMetrics.getPlanSummary();
    List<Map<String, Object>> indexesUsed = planSummary.getIndexesUsed();
    System.out.println("----- Indexes Used -----");
    indexesUsed.forEach(map -> map.forEach((key, val) -> System.out.println(key + ": " + val)));

    if (!results.hasNext()) {
      throw new Exception("query yielded no results");
    }

    // Get the query results
    System.out.println("----- Query Results -----");
    while (results.hasNext()) {
      Entity entity = results.next();
      System.out.printf("Entity: %s%n", entity);
    }
  }
}

Consultez l'objet executionStats pour obtenir des informations sur le profilage des requêtes, par exemple :

{
    "resultsReturned": "5",
    "executionDuration": "0.100718s",
    "readOperations": "5",
    "debugStats": {
               "index_entries_scanned": "95000",
               "documents_scanned": "5"
               "billing_details": {
                     "documents_billable": "5",
                     "index_entries_billable": "0",
                     "small_ops": "0",
                     "min_query_cost": "0",
               }
    }
}

Pour en savoir plus sur ce rapport, consultez la documentation de référence.

Interpréter les résultats et procéder à des ajustements

L'exemple de scénario suivant permet d'interroger les films par genre et pays de production et montre comment optimiser les index utilisés requête.

Pour en savoir plus sur ce rapport, consultez la documentation de référence sur le rapport "Explication des requêtes".

Pour illustration, supposons l'équivalent de cette requête SQL.

SELECT *
FROM movies
WHERE category = 'Romantic' AND country = 'USA';

Si nous utilisons l'option d'analyse, le rapport suivant montre La requête s'exécute sur des index à champ unique (category ASC, __name__ ASC). (country ASC, __name__ ASC) Il analyse 16 500 entrées d'index, mais ne renvoie que 1 200 documents.

// Output query planning info
"indexes_used": [
    {"query_scope": "Collection Group", "properties": "(category ASC, __name__ ASC)"},
    {"query_scope": "Collection Group", "properties": "(country ASC, __name__ ASC)"},
]

// Output query status
{
    "resultsReturned": "1200",
    "executionDuration": "0.118882s",
    "readOperations": "1200",
    "debugStats": {
               "index_entries_scanned": "16500",
               "documents_scanned": "1200"
               "billing_details": {
                     "documents_billable": "1200",
                     "index_entries_billable": "0",
                     "small_ops": "0",
                     "min_query_cost": "0",
               }
    }
}

Pour optimiser les performances de l'exécution de la requête, vous pouvez créer un index composite entièrement couvert (catégorie ASC, pays ASC, __nom__ ASC).

En exécutant à nouveau la requête en mode analyse, nous pouvons voir que l'index nouvellement créé est sélectionné pour cette requête, et qu'elle s'exécute beaucoup plus rapidement et plus efficacement.

// Output query planning info
    "indexes_used": [
        {"query_scope": "Collection Group", "properties": "(category ASC, country ASC, __name__ ASC)"}
        ]

// Output query stats
{
    "resultsReturned": "1200",
    "executionDuration": "0.026139s",
    "readOperations": "1200",
    "debugStats": {
               "index_entries_scanned": "1200",
               "documents_scanned": "1200"
               "billing_details": {
                     "documents_billable": "1200",
                     "index_entries_billable": "0",
                     "small_ops": "0",
                     "min_query_cost": "0",
               }
    }
}

Étape suivante