Utiliser Query Explain

Query Explain 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 de ces requêtes. 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 Query Explain à l'aide des 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, en vous montrant 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é.
  • Vous aide à comprendre vos coûts et vos performances pour chaque requête, et vous permet d'itérer rapidement différents modèles de requête afin d'optimiser leur utilisation.

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

Les opérations Query Explain peuvent être effectuées à l'aide de l'option default (par défaut) ou de l'option analyze (analyse).

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 les étapes du planificateur. Cela vous permet de vérifier qu'une requête dispose des index nécessaires et d'identifier les index utilisés. Cela vous aidera à vérifier, par exemple, qu'une requête particulière utilise un index composite plutôt que de devoir se croiser sur de nombreux index différents.

Avec l'option d'analyse, l'option "Interroger Explain" permet de planifier et d'exécuter la requête. Cette commande renvoie toutes les informations de planification mentionnées précédemment, ainsi que les statistiques de l'environnement d'exécution de la requête. Elles incluront des informations de facturation ainsi que des insights au niveau du système sur l'exécution de la requête. 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 le service Query Explain ?

Lorsqu'une requête est expliquée avec l'option par défaut, aucune opération d'index ou 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'index et de lecture sont effectuées. La requête vous est donc facturée comme d'habitude. Aucuns frais supplémentaires ne sont facturés pour l'activité d'analyse. Seuls les frais habituels pour la requête en cours d'exécution sont facturés.

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 de requête sont authentifiés par Identity and Access Management, avec les mêmes autorisations 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 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 par Identity and Access Management (IAM), qui utilisent les mêmes autorisations 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.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 de profilage de requête 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 procéder à 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 Query Explain.

À titre d'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 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 complet (catégorie ASC, pays ASC, __name__ ASC).

En exécutant à nouveau la requête en mode d'analyse, nous constatons que l'index nouvellement créé est sélectionné pour cette requête, et que celle-ci 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