Utiliser l'explication des requêtes

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

Vous pouvez envoyer des requêtes "Expliquer la requête" à l'aide des bibliothèques clientes en mode Datastore.

Les résultats de l'explication des requêtes vous aident à comprendre comment vos requêtes sont exécutées. Ils vous indiquent les inefficacités et l'emplacement des goulots d'étranglement probables côté serveur.

Explication de la requête :

  • Fournit des insights sur la phase de planification pour vous permettre d'ajuster vos index de requête et d'améliorer l'efficacité.
  • Vous aide à comprendre vos coûts et vos performances pour chaque requête, et vous permet de parcourir rapidement différents modèles de requêtes afin d'optimiser leur utilisation.

Comprendre les options "Expliquer la requête" : "Par défaut" et "Analyser"

Les opérations Explain de requête peuvent être effectuées à l'aide de l'option default ou analyze.

Avec l'option par défaut, Query Explain planifie la requête, mais ignore l'étape d'exécution. Cette opération renvoie des informations sur l'étape de planification. Vous pouvez l'utiliser pour vérifier qu'une requête dispose des index nécessaires et comprendre quels index sont utilisés. Cela vous aidera, par exemple, à vérifier qu'une requête spécifique utilise un index composite plutôt que d'avoir à effectuer une intersection sur de nombreux index différents.

Avec l'option "Analyser", Query Explain planifie et exécute la requête. Cette commande renvoie toutes les informations sur le planificateur mentionnées précédemment, ainsi que des statistiques sur le temps d'exécution de la requête. Cela inclut les informations de facturation ainsi que des insights au niveau du système sur l'exécution des requêtes. Vous pouvez utiliser cet outil pour tester différentes configurations de requêtes et d'index afin d'optimiser leur coût et leur latence.

Combien coûte l'explication des requêtes ?

Lorsqu'une requête est expliquée avec l'option par défaut, aucune opération d'index 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 "Analyser", des opérations d'indexation et de lecture sont effectuées. Vous êtes donc facturé pour la requête comme d'habitude. L'activité d'analyse n'entraîne aucun frais supplémentaire. Seuls les frais habituels pour la requête exécutée sont facturés.

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

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

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

Java

Pour savoir comment installer et utiliser la bibliothèque cliente pour le mode Datastore, consultez la page 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.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 le rapport, consultez la documentation de référence sur les rapports.

Exécuter une requête avec l'option "Analyser"

Vous pouvez utiliser une bibliothèque cliente pour envoyer une demande 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 que pour les opérations de requête classiques.

Java

Pour savoir comment installer et utiliser la bibliothèque cliente pour le mode Datastore, consultez la page 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 trouver des informations sur le profilage des requêtes, telles que :

{
    "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 le rapport, consultez la documentation de référence sur les rapports.

Interpréter les résultats et effectuer des ajustements

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

Pour en savoir plus sur le rapport, consultez la documentation de référence sur le rapport "Expliquer la requête".

Pour illustrer cela, prenons pour exemple l'équivalent de cette requête SQL.

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

Si nous utilisons l'option d'analyse, le résultat du rapport suivant montre que la requête s'exécute sur les index à champ unique (category ASC, __name__ ASC) et (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 d'exécution de la requête, vous pouvez créer un index composite entièrement couvert (category ASC, country ASC, __name__ ASC).

Si nous exécutons à nouveau la requête en mode Analyse, nous pouvons voir que l'index nouvellement créé est sélectionné pour cette requête, et que la requête 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",
               }
    }
}

Étapes suivantes