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 détaillées sur les performances de l'exécution des requêtes backend. 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 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 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 par requête et vous permet d'itérer rapidement sur différents modèles de requêtes afin d'optimiser leur utilisation.

Comprendre les options d'explication des requêtes: par défaut et analyse

Les opérations d'explication des requêtes peuvent être effectuées à l'aide de l'option par défaut ou analyser.

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 l'utiliser pour vérifier qu'une requête dispose des index nécessaires et identifier les index utilisés. Cela vous aidera à vérifier, par exemple, qu'une requête particulière utilise un indice composite plutôt que d'avoir à s'intersecter avec de nombreux indices différents.

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.

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'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 régulières.

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 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 ce rapport, consultez la documentation de référence.

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 d'analyse des requêtes sont authentifiés avec Identity and Access Management (IAM), à 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 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 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 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, la sortie du rapport suivante 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 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