Query Explain を使用する

Query Explain を使用すると、Datastore モードのクエリをバックエンドに送信し、バックエンド クエリの実行に関する詳細なパフォーマンス統計情報を受け取ることができます。これは、多くのリレーショナル データベース システムの EXPLAIN ANALYZE オペレーションのように機能します。

Query Explain リクエストは、Datastore モードのクライアント ライブラリを使用して送信できます。

Query Explain の結果はクエリがどのように実行されるかを理解するうえで役立ち、非効率性やサーバー側でボトルネックになりそうな箇所を示します。

Query Explain:

  • クエリのインデックスを調整して効率性を高めることができるように、計画フェーズに関する分析情報が提供されます。
  • クエリごとにコストとパフォーマンスを把握でき、さまざまなクエリパターンを迅速に反復してその使用方法を最適化できます。

Query Explain のオプションについて: デフォルトと分析

Query Explain のオペレーションは、「デフォルト」オプションまたは「分析」オプションを使用して実行できます。

デフォルトのオプションでは、Query Explain はクエリを計画するものの実行ステージはスキップします。これによりプランナー ステージの情報が返されます。この情報を使用すると、クエリに必要なインデックスがあることを確認して、どのインデックスが使用されているかを把握できます。これは、たとえば、特定のクエリがさまざまなインデックスを交差させることなく、複合インデックスを使用していることを確認するのに役立ちます。

分析オプションを使用すると、Query Explain はクエリの計画と実行の両方を行います。これにより、前述のすべてのプランナー情報と、クエリ実行ランタイムからの統計情報が返されます。これには、課金情報とクエリ実行に関するシステムレベルの分析情報が含まれます。このツールを使用すると、さまざまなクエリとインデックスの構成をテストしてコストとレイテンシを最適化できます。

Query Explain のコストは?

クエリがデフォルト オプションで説明されると、インデックス オペレーションや読み取りオペレーションは実行されません。クエリの複雑さに関係なく、1 回の読み取りオペレーションが課金されます。

クエリが分析オプションで説明されると、インデックス オペレーションと読み取りオペレーションが実行されるため、通常のクエリに対する料金が発生します。分析アクティビティに対して追加料金はかからず、クエリ実行に対する通常の料金のみが発生します。

デフォルトのオプションを使用してクエリを実行する

クライアント ライブラリを使用して、デフォルトのオプション リクエストを送信できます。

クエリの説明の結果は、通常のクエリ オペレーションと同じ権限を使用して Identity and Access Management で認証されます。

Java

Datastore モードのクライアント ライブラリをインストールして使用する方法については、Datastore モードのクライアント ライブラリをご覧ください。詳細については、Datastore モードの Java API のリファレンス ドキュメントをご覧ください。

Datastore モードへの認証を行うには、アプリケーションのデフォルト認証情報を設定します。 詳細については、ローカル開発環境の認証の設定をご覧ください。


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)));
  }
}

クエリプランで使用されるインデックスについては、レスポンスの indexes_used フィールドをご覧ください。

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

レポートの詳細については、レポート リファレンスをご覧ください。

分析オプションを使用してクエリを実行する

クライアント ライブラリを使用して、デフォルトのオプション リクエストを送信できます。

クエリ分析の結果は、通常のクエリ オペレーションと同じ権限を使用して、Identity and Access Management(IAM)で認証されます。

Java

Datastore モードのクライアント ライブラリをインストールして使用する方法については、Datastore モードのクライアント ライブラリをご覧ください。詳細については、Datastore モードの Java API のリファレンス ドキュメントをご覧ください。

Datastore モードへの認証を行うには、アプリケーションのデフォルト認証情報を設定します。 詳細については、ローカル開発環境の認証の設定をご覧ください。

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);
    }
  }
}

次のようなクエリ プロファイリング情報を確認するには、executionStats オブジェクトをご覧ください。

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

レポートの詳細については、レポート リファレンスをご覧ください。

結果を解釈して調整を行う

次のシナリオ例では、ジャンル別、製作国別に映画のクエリを実行して、クエリで使用されるインデックスを最適化する方法を示します。

レポートの詳細については、Query Explain レポート リファレンスをご覧ください。

たとえば、以下の SQL クエリと同等のものを想定します。

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

分析オプションを使用すると、以下のレポート出力は、単一フィールド インデックス (category ASC, __name__ ASC)(country ASC, __name__ ASC) でクエリが実行されることを示します。16,500 件のインデックス エントリをスキャンしますが、ドキュメントは 1,200 件しか返されません。

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

クエリの実行パフォーマンスを最適化するには、完全にカバーされた複合インデックス(カテゴリ ASC、国 ASC、__name__ ASC)を作成します。

クエリを再度分析モードで実行すると、新たに作成されたインデックスがこのクエリで選択されており、クエリがより高速かつ効率的に実行されることがわかります。

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

次のステップ