Usar Explicación de consulta

Query Explain te permite enviar consultas en modo Datastore al backend y recibir estadísticas de rendimiento detalladas sobre la ejecución de consultas de backend. Funciona como la operación EXPLAIN ANALYZE en muchos sistemas de bases de datos relacionales.

Puedes enviar solicitudes de explicación de consultas mediante las bibliotecas de cliente del modo Datastore.

Los resultados de Explicación de la consulta te ayudan a entender cómo se ejecutan tus consultas, ya que te muestran las ineficiencias y la ubicación de los posibles cuellos de botella del lado del servidor.

Explicación de la consulta:

  • Proporciona estadísticas sobre la fase de planificación para que puedas ajustar los índices de tus consultas y aumentar la eficiencia.
  • Te ayuda a conocer los costes y el rendimiento de cada consulta y te permite iterar rápidamente entre diferentes patrones de consulta para optimizar su uso.

Información sobre las opciones de explicación de consultas: predeterminada y analizar

Las operaciones de Query Explain se pueden realizar con la opción predeterminada o con la opción analizar.

Con la opción predeterminada, Query Explain planifica la consulta, pero se salta la fase de ejecución. Se devolverá información sobre la fase de planificación. Puedes usar esta función para comprobar si una consulta tiene los índices necesarios y saber qué índices se usan. De esta forma, podrás verificar, por ejemplo, que una consulta concreta está usando un índice compuesto en lugar de tener que intersectar muchos índices diferentes.

Con la opción de analizar, Query Explain planifica y ejecuta la consulta. De esta forma, se devuelve toda la información del planificador mencionada anteriormente, junto con las estadísticas del tiempo de ejecución de la consulta. Esto incluirá información de facturación, así como estadísticas a nivel de sistema sobre la ejecución de la consulta. Puedes usar esta herramienta para probar varias configuraciones de consultas e índices con el objetivo de optimizar su coste y latencia.

¿Qué coste tiene Query Explain?

Cuando se explica una consulta con la opción predeterminada, no se realizan operaciones de lectura ni de índice. Independientemente de la complejidad de la consulta, se cobra una operación de lectura.

Cuando se explica una consulta con la opción de analizar, se realizan operaciones de lectura e índice, por lo que se te cobra por la consulta como de costumbre. No se aplica ningún cargo adicional por la actividad de análisis, solo el cargo habitual por la consulta que se ejecuta.

Ejecutar una consulta con la opción predeterminada

Puedes usar una biblioteca de cliente para enviar una solicitud de opción predeterminada.

Ten en cuenta que los resultados de la explicación de la consulta se autentican con Gestión de Identidades y Accesos, con los mismos permisos que las operaciones de consulta normales.

Java

Para saber cómo instalar y usar la biblioteca de cliente del modo Datastore, consulta Bibliotecas de cliente del modo Datastore. Para obtener más información, consulta la documentación de referencia de la API del modo Datastore Java.

Para autenticarte en el modo Datastore, configura las credenciales predeterminadas de la aplicación. Para obtener más información, consulta el artículo Configurar la autenticación en un entorno de desarrollo 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)));
  }
}

Consulta el campo indexes_used de la respuesta para obtener información sobre los índices utilizados en el plan de consulta:

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

Para obtener más información sobre el informe, consulta la referencia del informe.

Ejecutar una consulta con la opción de análisis

Puedes usar una biblioteca de cliente para enviar una solicitud de opción predeterminada.

Ten en cuenta que los resultados del análisis de consultas se autentican con Gestión de Identidades y Accesos (IAM), con los mismos permisos que las operaciones de consulta normales.

Java

Para saber cómo instalar y usar la biblioteca de cliente del modo Datastore, consulta Bibliotecas de cliente del modo Datastore. Para obtener más información, consulta la documentación de referencia de la API del modo Datastore Java.

Para autenticarte en el modo Datastore, configura las credenciales predeterminadas de la aplicación. Para obtener más información, consulta el artículo Configurar la autenticación en un entorno de desarrollo 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);
    }
  }
}

Consulta el objeto executionStats para obtener información sobre la creación de perfiles de consultas, como la siguiente:

{
    "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",
               }
    }
}

Para obtener más información sobre el informe, consulta la referencia del informe.

Interpretar los resultados y hacer ajustes

En el siguiente ejemplo se consultan películas por género y país de producción, y se muestra cómo optimizar los índices que usa la consulta.

Para obtener más información sobre el informe, consulta la referencia del informe "Explicación de la consulta".

Para ilustrarlo, supongamos que se trata del equivalente de esta consulta de SQL.

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

Si usamos la opción de análisis, el siguiente resultado del informe muestra que la consulta se ejecuta en los índices de un solo campo (category ASC, __name__ ASC) y (country ASC, __name__ ASC). Analiza 16.500 entradas de índice, pero devuelve solo 1200 documentos.

// 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",
               }
    }
}

Para optimizar el rendimiento de la ejecución de la consulta, puedes crear un índice compuesto totalmente cubierto (category ASC, country ASC, __name__ ASC).

Si volvemos a ejecutar la consulta en el modo de análisis, podemos ver que se ha seleccionado el índice recién creado para esta consulta, que se ejecuta mucho más rápido y de forma más eficiente.

// 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",
               }
    }
}

Siguientes pasos