Usar o Query Explain

O Query Explain permite enviar consultas do modo Datastore para o back-end e receber estatísticas detalhadas de desempenho sobre a execução de consultas de back-end em troca. Ela funciona como a operação EXPLAIN ANALYZE em muitas sistemas de bancos de dados relacionais.

É possível enviar solicitações de explicação de consulta usando as bibliotecas de cliente do modo Datastore.

Os resultados do Query Explain ajudam a entender como suas consultas são executadas, mostrando ineficiências e o local de prováveis gargalos do lado do servidor.

Explicação da consulta:

  • Fornece insights sobre a fase de planejamento para que você possa ajustar seus índices de consulta e aumentar a eficiência.
  • Ajuda a entender o custo e o desempenho em cada consulta e permite acessar rapidamente diferentes padrões de consulta para otimizar o uso deles.

Entender as opções de Query Explain: default e analyze

As operações do Query Explain podem ser realizadas usando a opção default ou analyze.

Com a opção padrão, a explicação de consulta planeja a consulta, mas ignora no estágio de execução. Isso retornará informações do estágio do planejador. Você pode use isso para verificar se uma consulta tem os índices necessários e entender quais índices são usados. Isso vai ajudar você a verificar, por exemplo, se uma determinada consulta está usando um índice composto e ter de cruzar com muitas índices diferentes.

Com a opção de análise, a consulta explica os dois planos e executa o consulta. Isso retorna todas as informações do planejador mencionadas anteriormente, juntamente com estatísticas do ambiente de execução da consulta. Isso inclui informações de faturamento e insights no nível do sistema sobre a execução da consulta. É possível usar essas ferramentas para testar várias consultas e configurações de índice para otimizar o custo e a latência.

Qual é o custo da Query Explain?

Quando uma consulta é explicada com a opção padrão, nenhuma operação de índice ou leitura é realizada. Independentemente da complexidade da consulta, uma operação de leitura é cobrada.

Quando uma consulta é explicada com a opção de análise, operações de índice e leitura são executadas, portanto, a consulta é cobrada normalmente. Não há taxa extra pela atividade de análise, apenas a cobrança normal pela consulta que está sendo executada.

Executar uma consulta com a opção padrão

Você pode usar uma biblioteca de cliente para enviar uma solicitação de opção padrão.

Os resultados de explicação da consulta são autenticados com o gerenciamento de identidade e acesso, usando as mesmas permissões para operações de consulta regulares.

Java

Para saber como instalar e usar a biblioteca de cliente para o modo Datastore, consulte Bibliotecas de cliente do modo Datastore. Para mais informações, consulte a documentação de referência da API Java do modo do Datastore.

Para autenticar no modo Datastore, configure o Application Default Credentials. Para mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento 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)));
  }
}

Consulte o campo indexes_used na resposta para saber mais sobre os índices usados no plano de consulta:

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

Para mais informações sobre o relatório, consulte a referência do relatório.

Executar uma consulta com a opção de análise

Você pode usar uma biblioteca de cliente para enviar uma solicitação de opção padrão.

Os resultados da análise de consulta são autenticados com o Identity and Access Management (IAM), usando as mesmas permissões para operações de consulta regulares.

Java

Para saber como instalar e usar a biblioteca de cliente para o modo Datastore, consulte Bibliotecas de cliente do modo Datastore. Para mais informações, consulte a documentação de referência da API Java do modo do Datastore.

Para autenticar no modo Datastore, configure o Application Default Credentials. Para mais informações, consulte Configurar a autenticação para um ambiente de desenvolvimento 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);
    }
  }
}

Consulte o objeto executionStats para encontrar informações de perfil de consulta, como:

{
    "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 mais informações sobre o relatório, consulte a referência do relatório.

Interpretar resultados e fazer ajustes

O cenário de exemplo a seguir consulta filmes por gênero e país de produção e demonstra como otimizar os índices usados pela consulta.

Para mais informações sobre o relatório, consulte a referência do relatório do Query Explain.

Para ilustração, suponha o equivalente desta consulta SQL.

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

Se usarmos a opção de análise, a saída do relatório a seguir vai mostrar que a consulta é executada em índices de campo único (category ASC, __name__ ASC) e (country ASC, __name__ ASC). Ele verifica 16.500 entradas de índice, mas retorna somente 1.200 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 otimizar o desempenho da execução da consulta, crie um índice composto totalmente coberto (categoria ASC, país ASC, __nome__ ASC).

Ao executar a consulta no modo de análise novamente, podemos ver que o índice recém-criado é selecionado para essa consulta, e a consulta é executada muito mais rápido e com mais eficiência.

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

A seguir